From dbe0ede9a7148abd8b37a36aca3c356678a71ddb Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Fri, 28 Mar 2025 09:59:58 -0700 Subject: [PATCH 01/38] Handle null [x]ModelInfo types, recover from settings schema parse errors (#2064) --- src/core/config/ContextProxy.ts | 36 ++++++++++----- src/core/config/ProviderSettingsManager.ts | 14 +----- src/exports/roo-code.d.ts | 20 ++++----- src/exports/types.ts | 20 ++++----- src/schemas/index.ts | 10 ++--- .../src/components/settings/ApiOptions.tsx | 45 +++---------------- .../src/components/settings/ModelPicker.tsx | 26 ++++++----- .../settings/OpenRouterBalanceDisplay.tsx | 19 ++++++++ .../settings/RequestyBalanceDisplay.tsx | 21 +++++++++ 9 files changed, 112 insertions(+), 99 deletions(-) create mode 100644 webview-ui/src/components/settings/OpenRouterBalanceDisplay.tsx create mode 100644 webview-ui/src/components/settings/RequestyBalanceDisplay.tsx diff --git a/src/core/config/ContextProxy.ts b/src/core/config/ContextProxy.ts index 48c9846856..835ca214f6 100644 --- a/src/core/config/ContextProxy.ts +++ b/src/core/config/ContextProxy.ts @@ -2,16 +2,17 @@ import * as vscode from "vscode" import { PROVIDER_SETTINGS_KEYS, - ProviderSettings, - providerSettingsSchema, - GlobalSettings, - globalSettingsSchema, - RooCodeSettings, + GLOBAL_SETTINGS_KEYS, SECRET_STATE_KEYS, - SecretState, - isSecretStateKey, GLOBAL_STATE_KEYS, + ProviderSettings, + GlobalSettings, + SecretState, GlobalState, + RooCodeSettings, + providerSettingsSchema, + globalSettingsSchema, + isSecretStateKey, } from "../../schemas" import { logger } from "../../utils/logging" @@ -151,7 +152,15 @@ export class ContextProxy { */ public getGlobalSettings(): GlobalSettings { - return globalSettingsSchema.parse({ ...this.stateCache }) + const values = this.getValues() + + try { + return globalSettingsSchema.parse(values) + } catch (error) { + // Log to Posthog? + // We'll want to know about bad type assumptions or bad ExtensionState data. + return GLOBAL_SETTINGS_KEYS.reduce((acc, key) => ({ ...acc, [key]: values[key] }), {} as GlobalSettings) + } } /** @@ -159,7 +168,15 @@ export class ContextProxy { */ public getProviderSettings(): ProviderSettings { - return providerSettingsSchema.parse(this.getValues()) + const values = this.getValues() + + try { + return providerSettingsSchema.parse(values) + } catch (error) { + // Log to Posthog? + // We'll want to know about bad type assumptions or bad ExtensionState data. + return PROVIDER_SETTINGS_KEYS.reduce((acc, key) => ({ ...acc, [key]: values[key] }), {} as ProviderSettings) + } } public async setProviderSettings(values: ProviderSettings) { @@ -206,7 +223,6 @@ export class ContextProxy { public async export(): Promise { try { const globalSettings = globalSettingsExportSchema.parse(this.getValues()) - return Object.fromEntries(Object.entries(globalSettings).filter(([_, value]) => value !== undefined)) } catch (error) { console.log(error.message) diff --git a/src/core/config/ProviderSettingsManager.ts b/src/core/config/ProviderSettingsManager.ts index 471abe7e18..80904b401e 100644 --- a/src/core/config/ProviderSettingsManager.ts +++ b/src/core/config/ProviderSettingsManager.ts @@ -16,18 +16,6 @@ export const providerProfilesSchema = z.object({ export type ProviderProfiles = z.infer -const providerProfilesExportSchema = providerProfilesSchema.extend({ - apiConfigs: z.record( - z.string(), - providerSettingsWithIdSchema.omit({ - glamaModelInfo: true, - openRouterModelInfo: true, - unboundModelInfo: true, - requestyModelInfo: true, - }), - ), -}) - export class ProviderSettingsManager { private static readonly SCOPE_PREFIX = "roo_cline_config_" @@ -246,7 +234,7 @@ export class ProviderSettingsManager { public async export() { try { - return await this.lock(async () => providerProfilesExportSchema.parse(await this.load())) + return await this.lock(async () => providerProfilesSchema.parse(await this.load())) } catch (error) { throw new Error(`Failed to export provider profiles: ${error}`) } diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 12e86be8fd..87ac4306e8 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -27,7 +27,7 @@ type ProviderSettings = { anthropicBaseUrl?: string | undefined glamaModelId?: string | undefined glamaModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -40,13 +40,13 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined glamaApiKey?: string | undefined openRouterApiKey?: string | undefined openRouterModelId?: string | undefined openRouterModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -59,7 +59,7 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined openRouterBaseUrl?: string | undefined openRouterSpecificProvider?: string | undefined @@ -83,7 +83,7 @@ type ProviderSettings = { openAiR1FormatEnabled?: boolean | undefined openAiModelId?: string | undefined openAiCustomModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -96,7 +96,7 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined openAiUseAzure?: boolean | undefined azureApiVersion?: string | undefined @@ -125,7 +125,7 @@ type ProviderSettings = { unboundApiKey?: string | undefined unboundModelId?: string | undefined unboundModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -138,12 +138,12 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined requestyApiKey?: string | undefined requestyModelId?: string | undefined requestyModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -156,7 +156,7 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined modelTemperature?: (number | null) | undefined modelMaxTokens?: number | undefined diff --git a/src/exports/types.ts b/src/exports/types.ts index 9432537718..812a12b243 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -28,7 +28,7 @@ type ProviderSettings = { anthropicBaseUrl?: string | undefined glamaModelId?: string | undefined glamaModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -41,13 +41,13 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined glamaApiKey?: string | undefined openRouterApiKey?: string | undefined openRouterModelId?: string | undefined openRouterModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -60,7 +60,7 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined openRouterBaseUrl?: string | undefined openRouterSpecificProvider?: string | undefined @@ -84,7 +84,7 @@ type ProviderSettings = { openAiR1FormatEnabled?: boolean | undefined openAiModelId?: string | undefined openAiCustomModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -97,7 +97,7 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined openAiUseAzure?: boolean | undefined azureApiVersion?: string | undefined @@ -126,7 +126,7 @@ type ProviderSettings = { unboundApiKey?: string | undefined unboundModelId?: string | undefined unboundModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -139,12 +139,12 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined requestyApiKey?: string | undefined requestyModelId?: string | undefined requestyModelInfo?: - | { + | ({ maxTokens?: number | undefined contextWindow: number supportsImages?: boolean | undefined @@ -157,7 +157,7 @@ type ProviderSettings = { description?: string | undefined reasoningEffort?: ("low" | "medium" | "high") | undefined thinking?: boolean | undefined - } + } | null) | undefined modelTemperature?: (number | null) | undefined modelMaxTokens?: number | undefined diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 932fd490c9..72c02c61fc 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -314,12 +314,12 @@ export const providerSettingsSchema = z.object({ anthropicBaseUrl: z.string().optional(), // Glama glamaModelId: z.string().optional(), - glamaModelInfo: modelInfoSchema.optional(), + glamaModelInfo: modelInfoSchema.nullish(), glamaApiKey: z.string().optional(), // OpenRouter openRouterApiKey: z.string().optional(), openRouterModelId: z.string().optional(), - openRouterModelInfo: modelInfoSchema.optional(), + openRouterModelInfo: modelInfoSchema.nullish(), openRouterBaseUrl: z.string().optional(), openRouterSpecificProvider: z.string().optional(), openRouterUseMiddleOutTransform: z.boolean().optional(), @@ -344,7 +344,7 @@ export const providerSettingsSchema = z.object({ openAiApiKey: z.string().optional(), openAiR1FormatEnabled: z.boolean().optional(), openAiModelId: z.string().optional(), - openAiCustomModelInfo: modelInfoSchema.optional(), + openAiCustomModelInfo: modelInfoSchema.nullish(), openAiUseAzure: z.boolean().optional(), azureApiVersion: z.string().optional(), openAiStreamingEnabled: z.boolean().optional(), @@ -379,11 +379,11 @@ export const providerSettingsSchema = z.object({ // Unbound unboundApiKey: z.string().optional(), unboundModelId: z.string().optional(), - unboundModelInfo: modelInfoSchema.optional(), + unboundModelInfo: modelInfoSchema.nullish(), // Requesty requestyApiKey: z.string().optional(), requestyModelId: z.string().optional(), - requestyModelInfo: modelInfoSchema.optional(), + requestyModelInfo: modelInfoSchema.nullish(), // Claude 3.7 Sonnet Thinking modelTemperature: z.number().nullish(), modelMaxTokens: z.number().optional(), diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 40e329fca3..3ed1158f54 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -8,8 +8,6 @@ import { Checkbox } from "vscrui" import { VSCodeLink, VSCodeRadio, VSCodeRadioGroup, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { ExternalLinkIcon } from "@radix-ui/react-icons" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectSeparator, Button } from "@/components/ui" - import { ApiConfiguration, ModelInfo, @@ -42,56 +40,23 @@ import { import { ExtensionMessage } from "../../../../src/shared/ExtensionMessage" import { vscode } from "@/utils/vscode" +import { validateApiConfiguration, validateModelId, validateBedrockArn } from "@/utils/validate" import { useOpenRouterModelProviders, OPENROUTER_DEFAULT_PROVIDER_NAME, } from "@/components/ui/hooks/useOpenRouterModelProviders" -import { useOpenRouterKeyInfo } from "@/components/ui/hooks/useOpenRouterKeyInfo" -import { useRequestyKeyInfo } from "@/components/ui/hooks/useRequestyKeyInfo" +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectSeparator, Button } from "@/components/ui" + import { MODELS_BY_PROVIDER, PROVIDERS, AWS_REGIONS, VERTEX_REGIONS } from "./constants" import { VSCodeButtonLink } from "../common/VSCodeButtonLink" import { ModelInfoView } from "./ModelInfoView" import { ModelPicker } from "./ModelPicker" import { TemperatureControl } from "./TemperatureControl" -import { validateApiConfiguration, validateModelId, validateBedrockArn } from "@/utils/validate" import { ApiErrorMessage } from "./ApiErrorMessage" import { ThinkingBudget } from "./ThinkingBudget" import { R1FormatSetting } from "./R1FormatSetting" - -// Component to display OpenRouter API key balance -const OpenRouterBalanceDisplay = ({ apiKey, baseUrl }: { apiKey: string; baseUrl?: string }) => { - const { data: keyInfo } = useOpenRouterKeyInfo(apiKey, baseUrl) - - if (!keyInfo || !keyInfo.limit) { - return null - } - - const formattedBalance = (keyInfo.limit - keyInfo.usage).toFixed(2) - - return ( - - ${formattedBalance} - - ) -} - -const RequestyBalanceDisplay = ({ apiKey }: { apiKey: string }) => { - const { data: keyInfo } = useRequestyKeyInfo(apiKey) - - if (!keyInfo) { - return null - } - - // Parse the balance to a number and format it to 2 decimal places - const balance = parseFloat(keyInfo.org_balance) - const formattedBalance = balance.toFixed(2) - - return ( - - ${formattedBalance} - - ) -} +import { OpenRouterBalanceDisplay } from "./OpenRouterBalanceDisplay" +import { RequestyBalanceDisplay } from "./RequestyBalanceDisplay" interface ApiOptionsProps { uriScheme: string | undefined diff --git a/webview-ui/src/components/settings/ModelPicker.tsx b/webview-ui/src/components/settings/ModelPicker.tsx index e4e848478c..c8d8ed7a44 100644 --- a/webview-ui/src/components/settings/ModelPicker.tsx +++ b/webview-ui/src/components/settings/ModelPicker.tsx @@ -3,6 +3,8 @@ import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" import { Trans } from "react-i18next" import { ChevronsUpDown, Check, X } from "lucide-react" +import { ProviderSettings, ModelInfo } from "../../../../src/schemas" + import { useAppTranslation } from "@/i18n/TranslationContext" import { cn } from "@/lib/utils" import { @@ -18,30 +20,30 @@ import { Button, } from "@/components/ui" -import { ApiConfiguration, ModelInfo } from "../../../../src/shared/api" - import { normalizeApiConfiguration } from "./ApiOptions" import { ThinkingBudget } from "./ThinkingBudget" import { ModelInfoView } from "./ModelInfoView" -type ExtractType = NonNullable< - { [K in keyof ApiConfiguration]: Required[K] extends T ? K : never }[keyof ApiConfiguration] +type ModelIdKey = keyof Pick< + ProviderSettings, + "glamaModelId" | "openRouterModelId" | "unboundModelId" | "requestyModelId" | "openAiModelId" > -type ModelIdKeys = NonNullable< - { [K in keyof ApiConfiguration]: K extends `${string}ModelId` ? K : never }[keyof ApiConfiguration] +type ModelInfoKey = keyof Pick< + ProviderSettings, + "glamaModelInfo" | "openRouterModelInfo" | "unboundModelInfo" | "requestyModelInfo" | "openAiCustomModelInfo" > interface ModelPickerProps { defaultModelId: string defaultModelInfo?: ModelInfo models: Record | null - modelIdKey: ModelIdKeys - modelInfoKey: ExtractType + modelIdKey: ModelIdKey + modelInfoKey: ModelInfoKey serviceName: string serviceUrl: string - apiConfiguration: ApiConfiguration - setApiConfigurationField: (field: K, value: ApiConfiguration[K]) => void + apiConfiguration: ProviderSettings + setApiConfigurationField: (field: K, value: ProviderSettings[K]) => void } export const ModelPicker = ({ @@ -72,7 +74,9 @@ export const ModelPicker = ({ const onSelect = useCallback( (modelId: string) => { - if (!modelId) return + if (!modelId) { + return + } setOpen(false) const modelInfo = models?.[modelId] diff --git a/webview-ui/src/components/settings/OpenRouterBalanceDisplay.tsx b/webview-ui/src/components/settings/OpenRouterBalanceDisplay.tsx new file mode 100644 index 0000000000..fd081d1c56 --- /dev/null +++ b/webview-ui/src/components/settings/OpenRouterBalanceDisplay.tsx @@ -0,0 +1,19 @@ +import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" + +import { useOpenRouterKeyInfo } from "@/components/ui/hooks/useOpenRouterKeyInfo" + +export const OpenRouterBalanceDisplay = ({ apiKey, baseUrl }: { apiKey: string; baseUrl?: string }) => { + const { data: keyInfo } = useOpenRouterKeyInfo(apiKey, baseUrl) + + if (!keyInfo || !keyInfo.limit) { + return null + } + + const formattedBalance = (keyInfo.limit - keyInfo.usage).toFixed(2) + + return ( + + ${formattedBalance} + + ) +} diff --git a/webview-ui/src/components/settings/RequestyBalanceDisplay.tsx b/webview-ui/src/components/settings/RequestyBalanceDisplay.tsx new file mode 100644 index 0000000000..9eb9734499 --- /dev/null +++ b/webview-ui/src/components/settings/RequestyBalanceDisplay.tsx @@ -0,0 +1,21 @@ +import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" + +import { useRequestyKeyInfo } from "@/components/ui/hooks/useRequestyKeyInfo" + +export const RequestyBalanceDisplay = ({ apiKey }: { apiKey: string }) => { + const { data: keyInfo } = useRequestyKeyInfo(apiKey) + + if (!keyInfo) { + return null + } + + // Parse the balance to a number and format it to 2 decimal places. + const balance = parseFloat(keyInfo.org_balance) + const formattedBalance = balance.toFixed(2) + + return ( + + ${formattedBalance} + + ) +} From 105bc3c0d5a00583627356fe7b02f70d050e08af Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Fri, 28 Mar 2025 11:12:11 -0700 Subject: [PATCH 02/38] Require exactly node v20.18.1 (#2065) --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2dce74320f..582368ae72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -97,7 +97,7 @@ "zod-to-ts": "^1.2.0" }, "engines": { - "node": ">=20.18.1", + "node": "20.18.1", "vscode": "^1.84.0" } }, diff --git a/package.json b/package.json index cf2d7a5e66..d245cca60c 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "engines": { "vscode": "^1.84.0", - "node": ">=20.18.1" + "node": "20.18.1" }, "author": { "name": "Roo Code" From 5fbf56321624dbe5613707fdd63cbfaeea882489 Mon Sep 17 00:00:00 2001 From: Greg Taylor Date: Fri, 28 Mar 2025 20:48:03 -0700 Subject: [PATCH 03/38] Add an activation command for other extensions (#2073) Since VS Code provides no actionEvents that trigger when a specific extension activates, extensions can't activate only once Roo is activated and ready. This commit introduces a new roo-cline.activationCompleted command and fires it at the very end of extension activation. Other extensions that use Roo's exported APIs can now activate when they see this event. Co-authored-by: Greg Taylor --- src/activate/registerCommands.ts | 1 + src/extension.ts | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/activate/registerCommands.ts b/src/activate/registerCommands.ts index e9491f9371..827c3cd455 100644 --- a/src/activate/registerCommands.ts +++ b/src/activate/registerCommands.ts @@ -50,6 +50,7 @@ export const registerCommands = (options: RegisterCommandOptions) => { const getCommandsMap = ({ context, outputChannel, provider }: RegisterCommandOptions) => { return { + "roo-cline.activationCompleted": () => {}, "roo-cline.plusButtonClicked": async () => { await provider.removeClineFromStack() await provider.postStateToWebview() diff --git a/src/extension.ts b/src/extension.ts index a232cb51d0..1ce5c5a6b4 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -115,6 +115,9 @@ export async function activate(context: vscode.ExtensionContext) { registerCodeActions(context) registerTerminalActions(context) + // Allows other extensions to activate once Roo is ready. + vscode.commands.executeCommand('roo-cline.activationCompleted'); + // Implements the `RooCodeAPI` interface. return new API(outputChannel, provider) } From 683ff2fd8a07f023bc676d00a062ff1ca843b6f8 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Fri, 28 Mar 2025 22:05:14 -0600 Subject: [PATCH 04/38] Update bug_report.yml (#1994) * Update bug_report.yml * Update bug_report.yml --------- Co-authored-by: Matt Rubens --- .github/ISSUE_TEMPLATE/bug_report.yml | 142 ++++++++++++++------------ 1 file changed, 77 insertions(+), 65 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index dc66b4f390..91a6d5620b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,68 +1,80 @@ name: Bug Report -description: File a bug report +description: Clearly report a bug with detailed repro steps labels: ["bug"] body: - - type: input - id: version - attributes: - label: Which version of the app are you using? - description: Please specify the app version you're using (e.g. v3.3.1) - validations: - required: true - - type: dropdown - id: provider - attributes: - label: Which API Provider are you using? - multiple: false - options: - - OpenRouter - - Anthropic - - Google Gemini - - DeepSeek - - OpenAI - - OpenAI Compatible - - GCP Vertex AI - - AWS Bedrock - - Glama - - VS Code LM API - - LM Studio - - Ollama - validations: - required: true - - type: input - id: model - attributes: - label: Which Model are you using? - description: Please specify the model you're using (e.g. Claude 3.7 Sonnet) - validations: - required: true - - type: textarea - id: what-happened - attributes: - label: What happened? - description: Also tell us, what did you expect to happen? - placeholder: Tell us what you see! - validations: - required: true - - type: textarea - id: steps - attributes: - label: Steps to reproduce - description: How do you trigger this bug? Please walk us through it step by step. - value: | - 1. - 2. - 3. - validations: - required: true - - type: textarea - id: logs - attributes: - label: Relevant API REQUEST output - description: Please copy and paste any relevant output. This will be automatically formatted into code, so no need for backticks. - render: shell - - type: textarea - id: additional-context - attributes: - label: Additional context - description: Add any other context about the problem here, such as screenshots or related issues. + - type: input + id: version + attributes: + label: App Version + description: Specify exactly which version you're using (e.g., v3.3.1) + validations: + required: true + + - type: dropdown + id: provider + attributes: + label: API Provider + description: Choose the API provider involved + multiple: false + options: + - OpenRouter + - Anthropic + - Google Gemini + - DeepSeek + - OpenAI + - OpenAI Compatible + - GCP Vertex AI + - AWS Bedrock + - Requesty + - Glama + - VS Code LM API + - LM Studio + - Ollama + validations: + required: true + + - type: input + id: model + attributes: + label: Model Used + description: Clearly specify the exact model (e.g., Claude 3.7 Sonnet) + validations: + required: true + + - type: textarea + id: what-happened + attributes: + label: Actual vs. Expected Behavior + description: Clearly state what actually happened and what you expected instead. + placeholder: Provide precise details of the issue here. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Detailed Steps to Reproduce + description: | + List the exact steps someone must follow to reproduce this bug: + 1. Starting conditions (software state, settings, environment) + 2. Precise actions taken (every click, selection, input) + 3. Clearly observe and report outcomes + value: | + 1. + 2. + 3. + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant API Request Output + description: Paste relevant API logs or outputs here (formatted automatically as code) + render: shell + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Include extra details, screenshots, or related issues. From d7278d367e960e3f4f7b6506c1d9e69a12e5228f Mon Sep 17 00:00:00 2001 From: aheizi Date: Sat, 29 Mar 2025 12:09:28 +0800 Subject: [PATCH 05/38] Fix mention file name is not fully displayed (#2026) Fixed path leading character handling to preserve language characters and remove specific punctuation marks --- webview-ui/src/components/common/CodeAccordian.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/webview-ui/src/components/common/CodeAccordian.tsx b/webview-ui/src/components/common/CodeAccordian.tsx index 9d2f224ffb..468eade72f 100644 --- a/webview-ui/src/components/common/CodeAccordian.tsx +++ b/webview-ui/src/components/common/CodeAccordian.tsx @@ -17,12 +17,15 @@ interface CodeAccordianProps { } /* -We need to remove leading non-alphanumeric characters from the path in order for our leading ellipses trick to work. -^: Anchors the match to the start of the string. -[^a-zA-Z0-9]+: Matches one or more characters that are not alphanumeric. -The replace method removes these matched characters, effectively trimming the string up to the first alphanumeric character. +We need to remove certain leading characters from the path in order for our leading ellipses trick to work. +However, we want to preserve all language characters (including CJK, Cyrillic, etc.) and only remove specific +punctuation that might interfere with the ellipsis display. */ -export const removeLeadingNonAlphanumeric = (path: string): string => path.replace(/^[^a-zA-Z0-9]+/, "") +export const removeLeadingNonAlphanumeric = (path: string): string => { + // Only remove specific punctuation characters that might interfere with ellipsis display + // Keep all language characters (including CJK, Cyrillic, etc.) and numbers + return path.replace(/^[/\\:*?"<>|]+/, "") +} const CodeAccordian = ({ code, From cbe7075f4f3f8e52bbc84518d0fae0f5db959af2 Mon Sep 17 00:00:00 2001 From: Afshawn Lotfi <6283745+afshawnlotfi@users.noreply.github.com> Date: Sat, 29 Mar 2025 00:46:01 -0400 Subject: [PATCH 06/38] In-Editor Browser Improvements (#1601) * Browser Automation Improvements * Added multi-tab remote Chrome support * Added support for hover * Properly caching remote browser host in global state * Cleanup functions * Updated for changes after merge * Added www. exception for common tabs * Update src/core/webview/ClineProvider.ts * Revert README changes --------- Co-authored-by: Matt Rubens --- .gitignore | 1 + scripts/generate-types.mts | 3 +- src/core/Cline.ts | 7 +- src/core/mentions/index.ts | 5 +- src/core/webview/ClineProvider.ts | 90 +-- .../webview/__tests__/ClineProvider.test.ts | 82 +-- src/exports/roo-code.d.ts | 1 + src/exports/types.ts | 1 + src/schemas/index.ts | 7 +- src/services/browser/BrowserSession.ts | 519 +++++++++++------- src/services/browser/browserDiscovery.ts | 166 ++---- src/shared/ExtensionMessage.ts | 2 +- src/shared/WebviewMessage.ts | 1 - .../src/components/chat/ChatTextArea.tsx | 5 +- .../components/settings/BrowserSettings.tsx | 38 +- 15 files changed, 434 insertions(+), 494 deletions(-) diff --git a/.gitignore b/.gitignore index 02fdf8f88e..cc6551885f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.pnpm-store dist out out-* diff --git a/scripts/generate-types.mts b/scripts/generate-types.mts index c9bed9a003..2ad167b0d8 100644 --- a/scripts/generate-types.mts +++ b/scripts/generate-types.mts @@ -3,7 +3,8 @@ import fs from "fs/promises" import { zodToTs, createTypeAlias, printNode } from "zod-to-ts" import { $ } from "execa" -import { typeDefinitions } from "../src/schemas" +import schemas from "../src/schemas" +const { typeDefinitions } = schemas async function main() { const types: string[] = [ diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 7618a640eb..947cef016d 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2466,7 +2466,8 @@ export class Cline extends EventEmitter { } break } else { - let browserActionResult: BrowserActionResult + // Initialize with empty object to avoid "used before assigned" errors + let browserActionResult: BrowserActionResult = {} if (action === "launch") { if (!url) { this.consecutiveMistakeCount++ @@ -2552,9 +2553,9 @@ export class Cline extends EventEmitter { pushToolResult( formatResponse.toolResult( `The browser action has been executed. The console logs and screenshot have been captured for your analysis.\n\nConsole logs:\n${ - browserActionResult.logs || "(No new logs)" + browserActionResult?.logs || "(No new logs)" }\n\n(REMEMBER: if you need to proceed to using non-\`browser_action\` tools or launch a new browser, you MUST first close this browser. For example, if after analyzing the logs and screenshot you need to edit a file, you must first close the browser before you can use the write_to_file tool.)`, - browserActionResult.screenshot ? [browserActionResult.screenshot] : [], + browserActionResult?.screenshot ? [browserActionResult.screenshot] : [], ), ) break diff --git a/src/core/mentions/index.ts b/src/core/mentions/index.ts index e716359b7c..24696fe070 100644 --- a/src/core/mentions/index.ts +++ b/src/core/mentions/index.ts @@ -22,10 +22,7 @@ export async function openMention(mention?: string, osInfo?: string): Promise implements await this.postStateToWebview() break case "testBrowserConnection": - try { - const browserSession = new BrowserSession(this.context) - // If no text is provided, try auto-discovery - if (!message.text) { - try { - const discoveredHost = await discoverChromeInstances() - if (discoveredHost) { - // Test the connection to the discovered host - const result = await browserSession.testConnection(discoveredHost) - // Send the result back to the webview - await this.postMessageToWebview({ - type: "browserConnectionResult", - success: result.success, - text: `Auto-discovered and tested connection to Chrome at ${discoveredHost}: ${result.message}`, - values: { endpoint: result.endpoint }, - }) - } else { - await this.postMessageToWebview({ - type: "browserConnectionResult", - success: false, - text: "No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).", - }) - } - } catch (error) { - await this.postMessageToWebview({ - type: "browserConnectionResult", - success: false, - text: `Error during auto-discovery: ${error instanceof Error ? error.message : String(error)}`, - }) - } - } else { - // Test the provided URL - const result = await browserSession.testConnection(message.text) - + // If no text is provided, try auto-discovery + if (!message.text) { + // Use testBrowserConnection for auto-discovery + const chromeHostUrl = await discoverChromeHostUrl() + if (chromeHostUrl) { // Send the result back to the webview await this.postMessageToWebview({ type: "browserConnectionResult", - success: result.success, - text: result.message, - values: { endpoint: result.endpoint }, - }) - } - } catch (error) { - await this.postMessageToWebview({ - type: "browserConnectionResult", - success: false, - text: `Error testing connection: ${error instanceof Error ? error.message : String(error)}`, - }) - } - break - case "discoverBrowser": - try { - const discoveredHost = await discoverChromeInstances() - - if (discoveredHost) { - // Don't update the remoteBrowserHost state when auto-discovering - // This way we don't override the user's preference - - // Test the connection to get the endpoint - const browserSession = new BrowserSession(this.context) - const result = await browserSession.testConnection(discoveredHost) - - // Send the result back to the webview - await this.postMessageToWebview({ - type: "browserConnectionResult", - success: true, - text: `Successfully discovered and connected to Chrome at ${discoveredHost}`, - values: { endpoint: result.endpoint }, + success: !!chromeHostUrl, + text: `Auto-discovered and tested connection to Chrome: ${chromeHostUrl}`, + values: { endpoint: chromeHostUrl }, }) } else { await this.postMessageToWebview({ @@ -1496,11 +1439,17 @@ export class ClineProvider extends EventEmitter implements text: "No Chrome instances found on the network. Make sure Chrome is running with remote debugging enabled (--remote-debugging-port=9222).", }) } - } catch (error) { + } else { + // Test the provided URL + const customHostUrl = message.text + const hostIsValid = await tryChromeHostUrl(message.text) + // Send the result back to the webview await this.postMessageToWebview({ type: "browserConnectionResult", - success: false, - text: `Error discovering browser: ${error instanceof Error ? error.message : String(error)}`, + success: hostIsValid, + text: hostIsValid + ? `Successfully connected to Chrome: ${customHostUrl}` + : "Failed to connect to Chrome", }) } break @@ -2602,6 +2551,7 @@ export class ClineProvider extends EventEmitter implements screenshotQuality, remoteBrowserHost, remoteBrowserEnabled, + cachedChromeHostUrl, writeDelayMs, terminalOutputLineLimit, terminalShellIntegrationTimeout, @@ -2670,6 +2620,7 @@ export class ClineProvider extends EventEmitter implements screenshotQuality: screenshotQuality ?? 75, remoteBrowserHost, remoteBrowserEnabled: remoteBrowserEnabled ?? false, + cachedChromeHostUrl: cachedChromeHostUrl, writeDelayMs: writeDelayMs ?? 1000, terminalOutputLineLimit: terminalOutputLineLimit ?? 500, terminalShellIntegrationTimeout: terminalShellIntegrationTimeout ?? TERMINAL_SHELL_INTEGRATION_TIMEOUT, @@ -2755,6 +2706,7 @@ export class ClineProvider extends EventEmitter implements screenshotQuality: stateValues.screenshotQuality ?? 75, remoteBrowserHost: stateValues.remoteBrowserHost, remoteBrowserEnabled: stateValues.remoteBrowserEnabled ?? false, + cachedChromeHostUrl: stateValues.cachedChromeHostUrl as string | undefined, fuzzyMatchThreshold: stateValues.fuzzyMatchThreshold ?? 1.0, writeDelayMs: stateValues.writeDelayMs ?? 1000, terminalOutputLineLimit: stateValues.terminalOutputLineLimit ?? 500, diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index 5c859568eb..ea0677b010 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -40,9 +40,12 @@ jest.mock("../../../services/browser/BrowserSession", () => ({ // Mock browserDiscovery jest.mock("../../../services/browser/browserDiscovery", () => ({ - discoverChromeInstances: jest.fn().mockImplementation(async () => { + discoverChromeHostUrl: jest.fn().mockImplementation(async () => { return "http://localhost:9222" }), + tryChromeHostUrl: jest.fn().mockImplementation(async (url) => { + return url === "http://localhost:9222" + }), })) jest.mock( @@ -1916,9 +1919,9 @@ describe("ClineProvider", () => { type: "testBrowserConnection", }) - // Verify discoverChromeInstances was called - const { discoverChromeInstances } = require("../../../services/browser/browserDiscovery") - expect(discoverChromeInstances).toHaveBeenCalled() + // Verify discoverChromeHostUrl was called + const { discoverChromeHostUrl } = require("../../../services/browser/browserDiscovery") + expect(discoverChromeHostUrl).toHaveBeenCalled() // Verify postMessage was called with success result expect(mockPostMessage).toHaveBeenCalledWith( @@ -1929,77 +1932,6 @@ describe("ClineProvider", () => { }), ) }) - - test("handles discoverBrowser message", async () => { - // Get the message handler - const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] - - // Test browser discovery - await messageHandler({ - type: "discoverBrowser", - }) - - // Verify discoverChromeInstances was called - const { discoverChromeInstances } = require("../../../services/browser/browserDiscovery") - expect(discoverChromeInstances).toHaveBeenCalled() - - // Verify postMessage was called with success result - expect(mockPostMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: "browserConnectionResult", - success: true, - text: expect.stringContaining("Successfully discovered and connected to Chrome"), - }), - ) - }) - - test("handles errors during browser discovery", async () => { - // Mock discoverChromeInstances to throw an error - const { discoverChromeInstances } = require("../../../services/browser/browserDiscovery") - discoverChromeInstances.mockImplementationOnce(() => { - throw new Error("Discovery error") - }) - - // Get the message handler - const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] - - // Test browser discovery with error - await messageHandler({ - type: "discoverBrowser", - }) - - // Verify postMessage was called with error result - expect(mockPostMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: "browserConnectionResult", - success: false, - text: expect.stringContaining("Error discovering browser"), - }), - ) - }) - - test("handles case when no browsers are discovered", async () => { - // Mock discoverChromeInstances to return null (no browsers found) - const { discoverChromeInstances } = require("../../../services/browser/browserDiscovery") - discoverChromeInstances.mockImplementationOnce(() => null) - - // Get the message handler - const messageHandler = (mockWebviewView.webview.onDidReceiveMessage as jest.Mock).mock.calls[0][0] - - // Test browser discovery with no browsers found - await messageHandler({ - type: "discoverBrowser", - }) - - // Verify postMessage was called with failure result - expect(mockPostMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: "browserConnectionResult", - success: false, - text: expect.stringContaining("No Chrome instances found"), - }), - ) - }) }) }) diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 87ac4306e8..2f71c6662e 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -234,6 +234,7 @@ type GlobalSettings = { screenshotQuality?: number | undefined remoteBrowserEnabled?: boolean | undefined remoteBrowserHost?: string | undefined + cachedChromeHostUrl?: string | undefined enableCheckpoints?: boolean | undefined checkpointStorage?: ("task" | "workspace") | undefined ttsEnabled?: boolean | undefined diff --git a/src/exports/types.ts b/src/exports/types.ts index 812a12b243..fb3260d4f0 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -237,6 +237,7 @@ type GlobalSettings = { screenshotQuality?: number | undefined remoteBrowserEnabled?: boolean | undefined remoteBrowserHost?: string | undefined + cachedChromeHostUrl?: string | undefined enableCheckpoints?: boolean | undefined checkpointStorage?: ("task" | "workspace") | undefined ttsEnabled?: boolean | undefined diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 72c02c61fc..eef9ed3cd7 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -513,6 +513,7 @@ export const globalSettingsSchema = z.object({ screenshotQuality: z.number().optional(), remoteBrowserEnabled: z.boolean().optional(), remoteBrowserHost: z.string().optional(), + cachedChromeHostUrl: z.string().optional(), enableCheckpoints: z.boolean().optional(), checkpointStorage: checkpointStoragesSchema.optional(), @@ -618,6 +619,7 @@ const globalSettingsRecord: GlobalSettingsRecord = { customModePrompts: undefined, customSupportPrompts: undefined, enhancementApiConfigId: undefined, + cachedChromeHostUrl: undefined, } export const GLOBAL_SETTINGS_KEYS = Object.keys(globalSettingsRecord) as Keys[] @@ -791,7 +793,7 @@ export type TokenUsage = z.infer * TypeDefinition */ -type TypeDefinition = { +export type TypeDefinition = { schema: z.ZodTypeAny identifier: string } @@ -802,3 +804,6 @@ export const typeDefinitions: TypeDefinition[] = [ { schema: clineMessageSchema, identifier: "ClineMessage" }, { schema: tokenUsageSchema, identifier: "TokenUsage" }, ] + +// Also export as default for ESM compatibility +export default { typeDefinitions } diff --git a/src/services/browser/BrowserSession.ts b/src/services/browser/BrowserSession.ts index 5c5f59ffeb..7f8963fe1d 100644 --- a/src/services/browser/BrowserSession.ts +++ b/src/services/browser/BrowserSession.ts @@ -9,7 +9,7 @@ import delay from "delay" import axios from "axios" import { fileExistsAtPath } from "../../utils/fs" import { BrowserActionResult } from "../../shared/ExtensionMessage" -import { discoverChromeInstances, testBrowserConnection } from "./browserDiscovery" +import { discoverChromeHostUrl, tryChromeHostUrl } from "./browserDiscovery" interface PCRStats { puppeteer: { launch: typeof launch } @@ -21,20 +21,12 @@ export class BrowserSession { private browser?: Browser private page?: Page private currentMousePosition?: string - private cachedWebSocketEndpoint?: string - private lastConnectionAttempt: number = 0 + private lastConnectionAttempt?: number constructor(context: vscode.ExtensionContext) { this.context = context } - /** - * Test connection to a remote browser - */ - async testConnection(host: string): Promise<{ success: boolean; message: string; endpoint?: string }> { - return testBrowserConnection(host) - } - private async ensureChromiumExists(): Promise { const globalStoragePath = this.context?.globalStorageUri?.fsPath if (!globalStoragePath) { @@ -56,162 +48,173 @@ export class BrowserSession { return stats } - async launchBrowser(): Promise { - console.log("launch browser called") - if (this.browser) { - // throw new Error("Browser already launched") - await this.closeBrowser() // this may happen when the model launches a browser again after having used it already before - } + /** + * Gets the viewport size from global state or returns default + */ + private getViewport() { + const size = (this.context.globalState.get("browserViewportSize") as string | undefined) || "900x600" + const [width, height] = size.split("x").map(Number) + return { width, height } + } - // Function to get viewport size - const getViewport = () => { - const size = (this.context.globalState.get("browserViewportSize") as string | undefined) || "900x600" - const [width, height] = size.split("x").map(Number) - return { width, height } - } - - // Check if remote browser connection is enabled - const remoteBrowserEnabled = this.context.globalState.get("remoteBrowserEnabled") as boolean | undefined - - // If remote browser connection is not enabled, use local browser - if (!remoteBrowserEnabled) { - console.log("Remote browser connection is disabled, using local browser") - const stats = await this.ensureChromiumExists() - this.browser = await stats.puppeteer.launch({ - args: [ - "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", - ], - executablePath: stats.executablePath, - defaultViewport: getViewport(), - // headless: false, - }) - this.page = await this.browser?.newPage() - return - } - // Remote browser connection is enabled - let remoteBrowserHost = this.context.globalState.get("remoteBrowserHost") as string | undefined - let browserWSEndpoint: string | undefined = this.cachedWebSocketEndpoint - let reconnectionAttempted = false - - // Try to connect with cached endpoint first if it exists and is recent (less than 1 hour old) - if (browserWSEndpoint && Date.now() - this.lastConnectionAttempt < 3600000) { - try { - console.log(`Attempting to connect using cached WebSocket endpoint: ${browserWSEndpoint}`) - this.browser = await connect({ - browserWSEndpoint, - defaultViewport: getViewport(), - }) - this.page = await this.browser?.newPage() - return - } catch (error) { - console.log(`Failed to connect using cached endpoint: ${error}`) - // Clear the cached endpoint since it's no longer valid - this.cachedWebSocketEndpoint = undefined - // User wants to give up after one reconnection attempt - if (remoteBrowserHost) { - reconnectionAttempted = true - } - } - } - - // If user provided a remote browser host, try to connect to it - if (remoteBrowserHost && !reconnectionAttempted) { - console.log(`Attempting to connect to remote browser at ${remoteBrowserHost}`) - try { - // Fetch the WebSocket endpoint from the Chrome DevTools Protocol - const versionUrl = `${remoteBrowserHost.replace(/\/$/, "")}/json/version` - console.log(`Fetching WebSocket endpoint from ${versionUrl}`) - - const response = await axios.get(versionUrl) - browserWSEndpoint = response.data.webSocketDebuggerUrl - - if (!browserWSEndpoint) { - throw new Error("Could not find webSocketDebuggerUrl in the response") - } - - console.log(`Found WebSocket endpoint: ${browserWSEndpoint}`) - - // Cache the successful endpoint - this.cachedWebSocketEndpoint = browserWSEndpoint - this.lastConnectionAttempt = Date.now() - - this.browser = await connect({ - browserWSEndpoint, - defaultViewport: getViewport(), - }) - this.page = await this.browser?.newPage() - return - } catch (error) { - console.error(`Failed to connect to remote browser: ${error}`) - // Fall back to auto-discovery if remote connection fails - } - } - - // Always try auto-discovery if no custom URL is specified or if connection failed - try { - console.log("Attempting auto-discovery...") - const discoveredHost = await discoverChromeInstances() - - if (discoveredHost) { - console.log(`Auto-discovered Chrome at ${discoveredHost}`) - - // Don't save the discovered host to global state to avoid overriding user preference - // We'll just use it for this session - - // Try to connect to the discovered host - const testResult = await testBrowserConnection(discoveredHost) - - if (testResult.success && testResult.endpoint) { - // Cache the successful endpoint - this.cachedWebSocketEndpoint = testResult.endpoint - this.lastConnectionAttempt = Date.now() - - this.browser = await connect({ - browserWSEndpoint: testResult.endpoint, - defaultViewport: getViewport(), - }) - this.page = await this.browser?.newPage() - return - } - } - } catch (error) { - console.error(`Auto-discovery failed: ${error}`) - // Fall back to local browser if auto-discovery fails - } - - // If all remote connection attempts fail, fall back to local browser - console.log("Falling back to local browser") + /** + * Launches a local browser instance + */ + private async launchLocalBrowser(): Promise { + console.log("Launching local browser") const stats = await this.ensureChromiumExists() this.browser = await stats.puppeteer.launch({ args: [ "--user-agent=Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36", ], executablePath: stats.executablePath, - defaultViewport: getViewport(), + defaultViewport: this.getViewport(), // headless: false, }) - // (latest version of puppeteer does not add headless to user agent) - this.page = await this.browser?.newPage() } + /** + * Connects to a browser using a WebSocket URL + */ + private async connectWithChromeHostUrl(chromeHostUrl: string): Promise { + try { + this.browser = await connect({ + browserURL: chromeHostUrl, + defaultViewport: this.getViewport(), + }) + + // Cache the successful endpoint + console.log(`Connected to remote browser at ${chromeHostUrl}`) + this.context.globalState.update("cachedChromeHostUrl", chromeHostUrl) + this.lastConnectionAttempt = Date.now() + + return true + } catch (error) { + console.log(`Failed to connect using WebSocket endpoint: ${error}`) + return false + } + } + + /** + * Attempts to connect to a remote browser using various methods + * Returns true if connection was successful, false otherwise + */ + private async connectToRemoteBrowser(): Promise { + let remoteBrowserHost = this.context.globalState.get("remoteBrowserHost") as string | undefined + let reconnectionAttempted = false + + // Try to connect with cached endpoint first if it exists and is recent (less than 1 hour old) + const cachedChromeHostUrl = this.context.globalState.get("cachedChromeHostUrl") as string | undefined + if (cachedChromeHostUrl && this.lastConnectionAttempt && Date.now() - this.lastConnectionAttempt < 3_600_000) { + console.log(`Attempting to connect using cached Chrome Host Url: ${cachedChromeHostUrl}`) + if (await this.connectWithChromeHostUrl(cachedChromeHostUrl)) { + return true + } + + console.log(`Failed to connect using cached Chrome Host Url: ${cachedChromeHostUrl}`) + // Clear the cached endpoint since it's no longer valid + this.context.globalState.update("cachedChromeHostUrl", undefined) + + // User wants to give up after one reconnection attempt + if (remoteBrowserHost) { + reconnectionAttempted = true + } + } + + // If user provided a remote browser host, try to connect to it + else if (remoteBrowserHost && !reconnectionAttempted) { + console.log(`Attempting to connect to remote browser at ${remoteBrowserHost}`) + try { + const hostIsValid = await tryChromeHostUrl(remoteBrowserHost) + + if (!hostIsValid) { + throw new Error("Could not find chromeHostUrl in the response") + } + + console.log(`Found WebSocket endpoint: ${remoteBrowserHost}`) + + if (await this.connectWithChromeHostUrl(remoteBrowserHost)) { + return true + } + } catch (error) { + console.error(`Failed to connect to remote browser: ${error}`) + // Fall back to auto-discovery if remote connection fails + } + } + + try { + console.log("Attempting browser auto-discovery...") + const chromeHostUrl = await discoverChromeHostUrl() + + if (chromeHostUrl && (await this.connectWithChromeHostUrl(chromeHostUrl))) { + return true + } + } catch (error) { + console.error(`Auto-discovery failed: ${error}`) + // Fall back to local browser if auto-discovery fails + } + + return false + } + + async launchBrowser(): Promise { + console.log("launch browser called") + + // Check if remote browser connection is enabled + const remoteBrowserEnabled = this.context.globalState.get("remoteBrowserEnabled") as boolean | undefined + + if (!remoteBrowserEnabled) { + console.log("Launching local browser") + if (this.browser) { + // throw new Error("Browser already launched") + await this.closeBrowser() // this may happen when the model launches a browser again after having used it already before + } else { + // If browser wasn't open, just reset the state + this.resetBrowserState() + } + await this.launchLocalBrowser() + } else { + console.log("Connecting to remote browser") + // Remote browser connection is enabled + const remoteConnected = await this.connectToRemoteBrowser() + + // If all remote connection attempts fail, fall back to local browser + if (!remoteConnected) { + console.log("Falling back to local browser") + await this.launchLocalBrowser() + } + } + } + + /** + * Closes the browser and resets browser state + */ async closeBrowser(): Promise { if (this.browser || this.page) { console.log("closing browser...") - const remoteBrowserEnabled = this.context.globalState.get("remoteBrowserEnabled") as string | undefined + const remoteBrowserEnabled = this.context.globalState.get("remoteBrowserEnabled") as boolean | undefined if (remoteBrowserEnabled && this.browser) { await this.browser.disconnect().catch(() => {}) } else { await this.browser?.close().catch(() => {}) + this.resetBrowserState() } - this.browser = undefined - this.page = undefined - this.currentMousePosition = undefined + // this.resetBrowserState() } return {} } + /** + * Resets all browser state variables + */ + private resetBrowserState(): void { + this.browser = undefined + this.page = undefined + this.currentMousePosition = undefined + } + async doAction(action: (page: Page) => Promise): Promise { if (!this.page) { throw new Error( @@ -297,13 +300,118 @@ export class BrowserSession { } } - async navigateToUrl(url: string): Promise { - return this.doAction(async (page) => { - // networkidle2 isn't good enough since page may take some time to load. we can assume locally running dev sites will reach networkidle0 in a reasonable amount of time - await page.goto(url, { timeout: 7_000, waitUntil: ["domcontentloaded", "networkidle2"] }) - // await page.goto(url, { timeout: 10_000, waitUntil: "load" }) - await this.waitTillHTMLStable(page) // in case the page is loading more resources + /** + * Extract the root domain from a URL + * e.g., http://localhost:3000/path -> localhost:3000 + * e.g., https://example.com/path -> example.com + */ + private getRootDomain(url: string): string { + try { + const urlObj = new URL(url) + // Remove www. prefix if present + return urlObj.host.replace(/^www\./, "") + } catch (error) { + // If URL parsing fails, return the original URL + return url + } + } + + /** + * Navigate to a URL with standard loading options + */ + private async navigatePageToUrl(page: Page, url: string): Promise { + await page.goto(url, { timeout: 7_000, waitUntil: ["domcontentloaded", "networkidle2"] }) + await this.waitTillHTMLStable(page) + } + + /** + * Creates a new tab and navigates to the specified URL + */ + private async createNewTab(url: string): Promise { + if (!this.browser) { + throw new Error("Browser is not launched") + } + + // Create a new page + const newPage = await this.browser.newPage() + + // Set the new page as the active page + this.page = newPage + + // Navigate to the URL + const result = await this.doAction(async (page) => { + await this.navigatePageToUrl(page, url) }) + + return result + } + + async navigateToUrl(url: string): Promise { + if (!this.browser) { + throw new Error("Browser is not launched") + } + // Remove trailing slash for comparison + const normalizedNewUrl = url.replace(/\/$/, "") + + // Extract the root domain from the URL + const rootDomain = this.getRootDomain(normalizedNewUrl) + + // Get all current pages + const pages = await this.browser.pages() + + // Try to find a page with the same root domain + let existingPage: Page | undefined + + for (const page of pages) { + try { + const pageUrl = page.url() + if (pageUrl && this.getRootDomain(pageUrl) === rootDomain) { + existingPage = page + break + } + } catch (error) { + // Skip pages that might have been closed or have errors + console.log(`Error checking page URL: ${error}`) + continue + } + } + + if (existingPage) { + // Tab with the same root domain exists, switch to it + console.log(`Tab with domain ${rootDomain} already exists, switching to it`) + + // Update the active page + this.page = existingPage + existingPage.bringToFront() + + // Navigate to the new URL if it's different] + const currentUrl = existingPage.url().replace(/\/$/, "") // Remove trailing / if present + if (this.getRootDomain(currentUrl) === rootDomain && currentUrl !== normalizedNewUrl) { + console.log(`Navigating to new URL: ${normalizedNewUrl}`) + console.log(`Current URL: ${currentUrl}`) + console.log(`Root domain: ${this.getRootDomain(currentUrl)}`) + console.log(`New URL: ${normalizedNewUrl}`) + // Navigate to the new URL + return this.doAction(async (page) => { + await this.navigatePageToUrl(page, normalizedNewUrl) + }) + } else { + console.log(`Tab with domain ${rootDomain} already exists, and URL is the same: ${normalizedNewUrl}`) + // URL is the same, just reload the page to ensure it's up to date + console.log(`Reloading page: ${normalizedNewUrl}`) + console.log(`Current URL: ${currentUrl}`) + console.log(`Root domain: ${this.getRootDomain(currentUrl)}`) + console.log(`New URL: ${normalizedNewUrl}`) + return this.doAction(async (page) => { + await page.reload({ timeout: 7_000, waitUntil: ["domcontentloaded", "networkidle2"] }) + await this.waitTillHTMLStable(page) + }) + } + } else { + // No tab with this root domain exists, create a new one + console.log(`No tab with domain ${rootDomain} exists, creating a new one`) + return this.createNewTab(normalizedNewUrl) + } } // page.goto { waitUntil: "networkidle0" } may not ever resolve, and not waiting could return page content too early before js has loaded @@ -339,36 +447,50 @@ export class BrowserSession { } } - async click(coordinate: string): Promise { + /** + * Handles mouse interaction with network activity monitoring + */ + private async handleMouseInteraction( + page: Page, + coordinate: string, + action: (x: number, y: number) => Promise, + ): Promise { const [x, y] = coordinate.split(",").map(Number) + + // Set up network request monitoring + let hasNetworkActivity = false + const requestListener = () => { + hasNetworkActivity = true + } + page.on("request", requestListener) + + // Perform the mouse action + await action(x, y) + this.currentMousePosition = coordinate + + // Small delay to check if action triggered any network activity + await delay(100) + + if (hasNetworkActivity) { + // If we detected network activity, wait for navigation/loading + await page + .waitForNavigation({ + waitUntil: ["domcontentloaded", "networkidle2"], + timeout: 7000, + }) + .catch(() => {}) + await this.waitTillHTMLStable(page) + } + + // Clean up listener + page.off("request", requestListener) + } + + async click(coordinate: string): Promise { return this.doAction(async (page) => { - // Set up network request monitoring - let hasNetworkActivity = false - const requestListener = () => { - hasNetworkActivity = true - } - page.on("request", requestListener) - - // Perform the click - await page.mouse.click(x, y) - this.currentMousePosition = coordinate - - // Small delay to check if click triggered any network activity - await delay(100) - - if (hasNetworkActivity) { - // If we detected network activity, wait for navigation/loading - await page - .waitForNavigation({ - waitUntil: ["domcontentloaded", "networkidle2"], - timeout: 7000, - }) - .catch(() => {}) - await this.waitTillHTMLStable(page) - } - - // Clean up listener - page.off("request", requestListener) + await this.handleMouseInteraction(page, coordinate, async (x, y) => { + await page.mouse.click(x, y) + }) }) } @@ -378,31 +500,42 @@ export class BrowserSession { }) } + /** + * Scrolls the page by the specified amount + */ + private async scrollPage(page: Page, direction: "up" | "down"): Promise { + const { height } = this.getViewport() + const scrollAmount = direction === "down" ? height : -height + + await page.evaluate((scrollHeight) => { + window.scrollBy({ + top: scrollHeight, + behavior: "auto", + }) + }, scrollAmount) + + await delay(300) + } + async scrollDown(): Promise { - const size = ((await this.context.globalState.get("browserViewportSize")) as string | undefined) || "900x600" - const height = parseInt(size.split("x")[1]) return this.doAction(async (page) => { - await page.evaluate((scrollHeight) => { - window.scrollBy({ - top: scrollHeight, - behavior: "auto", - }) - }, height) - await delay(300) + await this.scrollPage(page, "down") }) } async scrollUp(): Promise { - const size = ((await this.context.globalState.get("browserViewportSize")) as string | undefined) || "900x600" - const height = parseInt(size.split("x")[1]) return this.doAction(async (page) => { - await page.evaluate((scrollHeight) => { - window.scrollBy({ - top: -scrollHeight, - behavior: "auto", - }) - }, height) - await delay(300) + await this.scrollPage(page, "up") + }) + } + + async hover(coordinate: string): Promise { + return this.doAction(async (page) => { + await this.handleMouseInteraction(page, coordinate, async (x, y) => { + await page.mouse.move(x, y) + // Small delay to allow any hover effects to appear + await delay(300) + }) }) } } diff --git a/src/services/browser/browserDiscovery.ts b/src/services/browser/browserDiscovery.ts index 187f90e299..b17e166a9b 100644 --- a/src/services/browser/browserDiscovery.ts +++ b/src/services/browser/browserDiscovery.ts @@ -1,7 +1,6 @@ -import * as vscode from "vscode" -import * as os from "os" import * as net from "net" import axios from "axios" +import * as dns from "dns" /** * Check if a port is open on a given host @@ -43,46 +42,14 @@ export async function isPortOpen(host: string, port: number, timeout = 1000): Pr /** * Try to connect to Chrome at a specific IP address */ -export async function tryConnect(ipAddress: string): Promise<{ endpoint: string; ip: string } | null> { +export async function tryChromeHostUrl(chromeHostUrl: string): Promise { try { - console.log(`Trying to connect to Chrome at: http://${ipAddress}:9222/json/version`) - const response = await axios.get(`http://${ipAddress}:9222/json/version`, { timeout: 1000 }) + console.log(`Trying to connect to Chrome at: ${chromeHostUrl}/json/version`) + const response = await axios.get(`${chromeHostUrl}/json/version`, { timeout: 1000 }) const data = response.data - return { endpoint: data.webSocketDebuggerUrl, ip: ipAddress } + return true } catch (error) { - return null - } -} - -/** - * Execute a shell command and return stdout and stderr - */ -export async function executeShellCommand(command: string): Promise<{ stdout: string; stderr: string }> { - return new Promise<{ stdout: string; stderr: string }>((resolve) => { - const cp = require("child_process") - cp.exec(command, (err: any, stdout: string, stderr: string) => { - resolve({ stdout, stderr }) - }) - }) -} - -/** - * Get Docker gateway IP without UI feedback - */ -export async function getDockerGatewayIP(): Promise { - try { - if (process.platform === "linux") { - try { - const { stdout } = await executeShellCommand("ip route | grep default | awk '{print $3}'") - return stdout.trim() - } catch (error) { - console.log("Could not determine Docker gateway IP:", error) - } - } - return null - } catch (error) { - console.log("Could not determine Docker gateway IP:", error) - return null + return false } } @@ -93,7 +60,6 @@ export async function getDockerHostIP(): Promise { try { // Try to resolve host.docker.internal (works on Docker Desktop) return new Promise((resolve) => { - const dns = require("dns") dns.lookup("host.docker.internal", (err: any, address: string) => { if (err) { resolve(null) @@ -111,7 +77,7 @@ export async function getDockerHostIP(): Promise { /** * Scan a network range for Chrome debugging port */ -export async function scanNetworkForChrome(baseIP: string): Promise { +export async function scanNetworkForChrome(baseIP: string, port: number): Promise { if (!baseIP || !baseIP.match(/^\d+\.\d+\.\d+\./)) { return null } @@ -130,7 +96,7 @@ export async function scanNetworkForChrome(baseIP: string): Promise { +// Function to discover Chrome instances on the network +const discoverChromeHosts = async (port: number): Promise => { // Get all network interfaces - const networkInterfaces = os.networkInterfaces() const ipAddresses = [] - // Always try localhost first - ipAddresses.push("localhost") - ipAddresses.push("127.0.0.1") - - // Try to get Docker gateway IP (headless mode) - const gatewayIP = await getDockerGatewayIP() - if (gatewayIP) { - console.log("Found Docker gateway IP:", gatewayIP) - ipAddresses.push(gatewayIP) - } - // Try to get Docker host IP const hostIP = await getDockerHostIP() if (hostIP) { @@ -166,44 +118,21 @@ export async function discoverChromeInstances(): Promise { ipAddresses.push(hostIP) } - // Add all local IP addresses from network interfaces - const localIPs: string[] = [] - Object.values(networkInterfaces).forEach((interfaces) => { - if (!interfaces) return - interfaces.forEach((iface) => { - // Only consider IPv4 addresses - if (iface.family === "IPv4" || iface.family === (4 as any)) { - localIPs.push(iface.address) - } - }) - }) - - // Add local IPs to the list - ipAddresses.push(...localIPs) - - // Scan network for Chrome debugging port - for (const ip of localIPs) { - const chromeIP = await scanNetworkForChrome(ip) - if (chromeIP && !ipAddresses.includes(chromeIP)) { - console.log("Found potential Chrome host via network scan:", chromeIP) - ipAddresses.push(chromeIP) - } - } - // Remove duplicates const uniqueIPs = [...new Set(ipAddresses)] console.log("IP Addresses to try:", uniqueIPs) // Try connecting to each IP address for (const ip of uniqueIPs) { - const connection = await tryConnect(ip) - if (connection) { - console.log(`Successfully connected to Chrome at: ${connection.ip}`) + const hostEndpoint = `http://${ip}:${port}` + + const hostIsValid = await tryChromeHostUrl(hostEndpoint) + if (hostIsValid) { // Store the successful IP for future use - console.log(`✅ Found Chrome at ${connection.ip} - You can hardcode this IP if needed`) + console.log(`✅ Found Chrome at ${hostEndpoint}`) // Return the host URL and endpoint - return `http://${connection.ip}:9222` + return hostEndpoint } } @@ -211,36 +140,43 @@ export async function discoverChromeInstances(): Promise { } /** - * Test connection to a remote browser + * Test connection to a remote browser debugging websocket. + * First tries specific hosts, then attempts auto-discovery if needed. + * @param browserHostUrl Optional specific host URL to check first + * @param port Browser debugging port (default: 9222) + * @returns WebSocket debugger URL if connection is successful, null otherwise */ -export async function testBrowserConnection( - host: string, -): Promise<{ success: boolean; message: string; endpoint?: string }> { - try { - // Fetch the WebSocket endpoint from the Chrome DevTools Protocol - const versionUrl = `${host.replace(/\/$/, "")}/json/version` - console.log(`Testing connection to ${versionUrl}`) +export async function discoverChromeHostUrl(port: number = 9222): Promise { + // First try specific hosts + const hostsToTry = [`http://localhost:${port}`, `http://127.0.0.1:${port}`] - const response = await axios.get(versionUrl, { timeout: 3000 }) - const browserWSEndpoint = response.data.webSocketDebuggerUrl - - if (!browserWSEndpoint) { - return { - success: false, - message: "Could not find webSocketDebuggerUrl in the response", - } - } - - return { - success: true, - message: "Successfully connected to Chrome browser", - endpoint: browserWSEndpoint, - } - } catch (error) { - console.error(`Failed to connect to remote browser: ${error}`) - return { - success: false, - message: `Failed to connect: ${error instanceof Error ? error.message : String(error)}`, + // Try each host directly first + for (const hostUrl of hostsToTry) { + console.log(`Trying to connect to: ${hostUrl}`) + try { + const hostIsValid = await tryChromeHostUrl(hostUrl) + if (hostIsValid) return hostUrl + } catch (error) { + console.log(`Failed to connect to ${hostUrl}: ${error instanceof Error ? error.message : error}`) } } + + // If direct connections failed, attempt auto-discovery + console.log("Direct connections failed. Attempting auto-discovery...") + + const discoveredHostUrl = await discoverChromeHosts(port) + if (discoveredHostUrl) { + console.log(`Trying to connect to discovered host: ${discoveredHostUrl}`) + try { + const hostIsValid = await tryChromeHostUrl(discoveredHostUrl) + if (hostIsValid) return discoveredHostUrl + console.log(`Failed to connect to discovered host ${discoveredHostUrl}`) + } catch (error) { + console.log(`Error connecting to discovered host: ${error instanceof Error ? error.message : error}`) + } + } else { + console.log("No browser instances discovered on network") + } + + return null } diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index eb5e89a2b4..60c20b6503 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -233,7 +233,7 @@ export interface ClineSayTool { } // Must keep in sync with system prompt. -export const browserActions = ["launch", "click", "type", "scroll_down", "scroll_up", "close"] as const +export const browserActions = ["launch", "click", "hover", "type", "scroll_down", "scroll_up", "close"] as const export type BrowserAction = (typeof browserActions)[number] diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 6b23d63b29..2cb1658988 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -115,7 +115,6 @@ export interface WebviewMessage { | "telemetrySetting" | "showRooIgnoredFiles" | "testBrowserConnection" - | "discoverBrowser" | "browserConnectionResult" | "remoteBrowserEnabled" | "language" diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index e7b0ca6d89..8bc681034e 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -70,12 +70,11 @@ const ChatTextArea = forwardRef( currentApiConfigName, listApiConfigMeta, customModes, - cwd, + cwd, osInfo, pinnedApiConfigs, togglePinnedApiConfig, - } = - useExtensionState() + } = useExtensionState() // Find the ID and display text for the currently selected API configuration const { currentConfigId, displayName } = useMemo(() => { diff --git a/webview-ui/src/components/settings/BrowserSettings.tsx b/webview-ui/src/components/settings/BrowserSettings.tsx index ee998ad2b4..d77ee16a8b 100644 --- a/webview-ui/src/components/settings/BrowserSettings.tsx +++ b/webview-ui/src/components/settings/BrowserSettings.tsx @@ -1,14 +1,14 @@ -import { HTMLAttributes, useState, useEffect, useMemo } from "react" import { VSCodeButton, VSCodeCheckbox, VSCodeTextField } from "@vscode/webview-ui-toolkit/react" import { SquareMousePointer } from "lucide-react" +import { HTMLAttributes, useEffect, useMemo, useState } from "react" -import { vscode } from "@/utils/vscode" -import { useAppTranslation } from "@/i18n/TranslationContext" import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, Slider } from "@/components/ui" +import { useAppTranslation } from "@/i18n/TranslationContext" +import { vscode } from "@/utils/vscode" -import { SetCachedStateField } from "./types" -import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" +import { SectionHeader } from "./SectionHeader" +import { SetCachedStateField } from "./types" type BrowserSettingsProps = HTMLAttributes & { browserToolEnabled?: boolean @@ -37,7 +37,7 @@ export const BrowserSettings = ({ const { t } = useAppTranslation() const [testingConnection, setTestingConnection] = useState(false) - const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null) + const [testResult, setTestResult] = useState<{ success: boolean; text: string } | null>(null) const [discovering, setDiscovering] = useState(false) // We don't need a local state for useRemoteBrowser since we're using the @@ -50,7 +50,7 @@ export const BrowserSettings = ({ const message = event.data if (message.type === "browserConnectionResult") { - setTestResult({ success: message.success, message: message.text }) + setTestResult({ success: message.success, text: message.text }) setTestingConnection(false) setDiscovering(false) } @@ -73,28 +73,12 @@ export const BrowserSettings = ({ } catch (error) { setTestResult({ success: false, - message: `Error: ${error instanceof Error ? error.message : String(error)}`, + text: `Error: ${error instanceof Error ? error.message : String(error)}`, }) setTestingConnection(false) } } - const discoverBrowser = async () => { - setDiscovering(true) - setTestResult(null) - - try { - // Send a message to the extension to discover Chrome instances. - vscode.postMessage({ type: "discoverBrowser" }) - } catch (error) { - setTestResult({ - success: false, - message: `Error: ${error instanceof Error ? error.message : String(error)}`, - }) - setDiscovering(false) - } - } - const options = useMemo( () => [ { @@ -206,9 +190,7 @@ export const BrowserSettings = ({ placeholder={t("settings:browser.remote.urlPlaceholder")} style={{ flexGrow: 1 }} /> - + {testingConnection || discovering ? t("settings:browser.remote.testingButton") : t("settings:browser.remote.testButton")} @@ -221,7 +203,7 @@ export const BrowserSettings = ({ ? "bg-green-800/20 text-green-400" : "bg-red-800/20 text-red-400" }`}> - {testResult.message} + {testResult.text} )}
From bedb7de72bf0e769f5a612bff7b84963f3151bcf Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sat, 29 Mar 2025 01:05:06 -0400 Subject: [PATCH 07/38] Add missing awaits on refactored tools (#2078) --- src/core/Cline.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 947cef016d..b83a7157cf 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2255,12 +2255,12 @@ export class Cline extends EventEmitter { } case "read_file": { - readFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + await readFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break } case "fetch_instructions": { - fetchInstructionsTool(this, block, askApproval, handleError, pushToolResult) + await fetchInstructionsTool(this, block, askApproval, handleError, pushToolResult) break } From e15fa227c5080838b1801946e2f7c273d9f5459f Mon Sep 17 00:00:00 2001 From: Nico Bihan Date: Sat, 29 Mar 2025 00:50:33 -0500 Subject: [PATCH 08/38] Added Gemini 2.5 Pro model to GCP Vertex AI Provider (#2079) --- src/shared/api.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/shared/api.ts b/src/shared/api.ts index d90dec6ad8..06f8cb82bf 100644 --- a/src/shared/api.ts +++ b/src/shared/api.ts @@ -464,6 +464,14 @@ export const vertexModels = { inputPrice: 0.15, outputPrice: 0.6, }, + "gemini-2.5-pro-exp-03-25": { + maxTokens: 65_535, + contextWindow: 1_048_576, + supportsImages: true, + supportsPromptCache: false, + inputPrice: 0, + outputPrice: 0, + }, "gemini-2.0-pro-exp-02-05": { maxTokens: 8192, contextWindow: 2_097_152, From 9bb50eaba37475b679634c51c973b6098f78be3e Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sat, 29 Mar 2025 02:14:18 -0400 Subject: [PATCH 09/38] Display info about partial reads in chat row (#2080) --- src/core/tools/readFileTool.ts | 22 +++++++++++++++++++--- src/i18n/locales/ca/tools.json | 9 +++++++++ src/i18n/locales/de/tools.json | 9 +++++++++ src/i18n/locales/en/tools.json | 9 +++++++++ src/i18n/locales/es/tools.json | 9 +++++++++ src/i18n/locales/fr/tools.json | 9 +++++++++ src/i18n/locales/hi/tools.json | 9 +++++++++ src/i18n/locales/it/tools.json | 9 +++++++++ src/i18n/locales/ja/tools.json | 9 +++++++++ src/i18n/locales/ko/tools.json | 9 +++++++++ src/i18n/locales/pl/tools.json | 9 +++++++++ src/i18n/locales/pt-BR/tools.json | 9 +++++++++ src/i18n/locales/tr/tools.json | 9 +++++++++ src/i18n/locales/vi/tools.json | 9 +++++++++ src/i18n/locales/zh-CN/tools.json | 9 +++++++++ src/i18n/locales/zh-TW/tools.json | 9 +++++++++ webview-ui/src/components/chat/ChatRow.tsx | 1 + 17 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 src/i18n/locales/ca/tools.json create mode 100644 src/i18n/locales/de/tools.json create mode 100644 src/i18n/locales/en/tools.json create mode 100644 src/i18n/locales/es/tools.json create mode 100644 src/i18n/locales/fr/tools.json create mode 100644 src/i18n/locales/hi/tools.json create mode 100644 src/i18n/locales/it/tools.json create mode 100644 src/i18n/locales/ja/tools.json create mode 100644 src/i18n/locales/ko/tools.json create mode 100644 src/i18n/locales/pl/tools.json create mode 100644 src/i18n/locales/pt-BR/tools.json create mode 100644 src/i18n/locales/tr/tools.json create mode 100644 src/i18n/locales/vi/tools.json create mode 100644 src/i18n/locales/zh-CN/tools.json create mode 100644 src/i18n/locales/zh-TW/tools.json diff --git a/src/core/tools/readFileTool.ts b/src/core/tools/readFileTool.ts index 6616a5fcd1..315f178e32 100644 --- a/src/core/tools/readFileTool.ts +++ b/src/core/tools/readFileTool.ts @@ -3,6 +3,7 @@ import { Cline } from "../Cline" import { ClineSayTool } from "../../shared/ExtensionMessage" import { ToolUse } from "../assistant-message" import { formatResponse } from "../prompts/responses" +import { t } from "../../i18n" import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" import { isPathOutsideWorkspace } from "../../utils/pathUtils" import { getReadablePath } from "../../utils/path" @@ -97,11 +98,29 @@ export async function readFileTool( break } + const { maxReadFileLine = 500 } = (await cline.providerRef.deref()?.getState()) ?? {} + + // Create line snippet description for approval message + let lineSnippet = "" + if (startLine !== undefined && endLine !== undefined) { + lineSnippet = t("tools:readFile.linesRange", { start: startLine + 1, end: endLine + 1 }) + } else if (startLine !== undefined) { + lineSnippet = t("tools:readFile.linesFromToEnd", { start: startLine + 1 }) + } else if (endLine !== undefined) { + lineSnippet = t("tools:readFile.linesFromStartTo", { end: endLine + 1 }) + } else if (maxReadFileLine === 0) { + lineSnippet = t("tools:readFile.definitionsOnly") + } else if (maxReadFileLine > 0) { + lineSnippet = t("tools:readFile.maxLines", { max: maxReadFileLine }) + } + cline.consecutiveMistakeCount = 0 const absolutePath = path.resolve(cline.cwd, relPath) + const completeMessage = JSON.stringify({ ...sharedMessageProps, content: absolutePath, + reason: lineSnippet, } satisfies ClineSayTool) const didApprove = await askApproval("tool", completeMessage) @@ -109,9 +128,6 @@ export async function readFileTool( break } - // Get the maxReadFileLine setting - const { maxReadFileLine = 500 } = (await cline.providerRef.deref()?.getState()) ?? {} - // Count total lines in the file let totalLines = 0 try { diff --git a/src/i18n/locales/ca/tools.json b/src/i18n/locales/ca/tools.json new file mode 100644 index 0000000000..14e7e43880 --- /dev/null +++ b/src/i18n/locales/ca/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (línies {{start}}-{{end}})", + "linesFromToEnd": " (línies {{start}}-final)", + "linesFromStartTo": " (línies 1-{{end}})", + "definitionsOnly": " (només definicions)", + "maxLines": " (màxim {{max}} línies)" + } +} diff --git a/src/i18n/locales/de/tools.json b/src/i18n/locales/de/tools.json new file mode 100644 index 0000000000..f1b7d85032 --- /dev/null +++ b/src/i18n/locales/de/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (Zeilen {{start}}-{{end}})", + "linesFromToEnd": " (Zeilen {{start}}-Ende)", + "linesFromStartTo": " (Zeilen 1-{{end}})", + "definitionsOnly": " (nur Definitionen)", + "maxLines": " (maximal {{max}} Zeilen)" + } +} diff --git a/src/i18n/locales/en/tools.json b/src/i18n/locales/en/tools.json new file mode 100644 index 0000000000..bb258961ba --- /dev/null +++ b/src/i18n/locales/en/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (lines {{start}}-{{end}})", + "linesFromToEnd": " (lines {{start}}-end)", + "linesFromStartTo": " (lines 1-{{end}})", + "definitionsOnly": " (definitions only)", + "maxLines": " (max {{max}} lines)" + } +} diff --git a/src/i18n/locales/es/tools.json b/src/i18n/locales/es/tools.json new file mode 100644 index 0000000000..f6e4389206 --- /dev/null +++ b/src/i18n/locales/es/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (líneas {{start}}-{{end}})", + "linesFromToEnd": " (líneas {{start}}-final)", + "linesFromStartTo": " (líneas 1-{{end}})", + "definitionsOnly": " (solo definiciones)", + "maxLines": " (máximo {{max}} líneas)" + } +} diff --git a/src/i18n/locales/fr/tools.json b/src/i18n/locales/fr/tools.json new file mode 100644 index 0000000000..97a640a18f --- /dev/null +++ b/src/i18n/locales/fr/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (lignes {{start}}-{{end}})", + "linesFromToEnd": " (lignes {{start}}-fin)", + "linesFromStartTo": " (lignes 1-{{end}})", + "definitionsOnly": " (définitions uniquement)", + "maxLines": " (max {{max}} lignes)" + } +} diff --git a/src/i18n/locales/hi/tools.json b/src/i18n/locales/hi/tools.json new file mode 100644 index 0000000000..7f682391f4 --- /dev/null +++ b/src/i18n/locales/hi/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (पंक्तियाँ {{start}}-{{end}})", + "linesFromToEnd": " (पंक्तियाँ {{start}}-अंत)", + "linesFromStartTo": " (पंक्तियाँ 1-{{end}})", + "definitionsOnly": " (केवल परिभाषाएँ)", + "maxLines": " (अधिकतम {{max}} पंक्तियाँ)" + } +} diff --git a/src/i18n/locales/it/tools.json b/src/i18n/locales/it/tools.json new file mode 100644 index 0000000000..a9ad538e9d --- /dev/null +++ b/src/i18n/locales/it/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (righe {{start}}-{{end}})", + "linesFromToEnd": " (righe {{start}}-fine)", + "linesFromStartTo": " (righe 1-{{end}})", + "definitionsOnly": " (solo definizioni)", + "maxLines": " (max {{max}} righe)" + } +} diff --git a/src/i18n/locales/ja/tools.json b/src/i18n/locales/ja/tools.json new file mode 100644 index 0000000000..6daed74793 --- /dev/null +++ b/src/i18n/locales/ja/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " ({{start}}-{{end}}行目)", + "linesFromToEnd": " ({{start}}行目-最後まで)", + "linesFromStartTo": " (1-{{end}}行目)", + "definitionsOnly": " (定義のみ)", + "maxLines": " (最大{{max}}行)" + } +} diff --git a/src/i18n/locales/ko/tools.json b/src/i18n/locales/ko/tools.json new file mode 100644 index 0000000000..f4583d2d06 --- /dev/null +++ b/src/i18n/locales/ko/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " ({{start}}-{{end}}행)", + "linesFromToEnd": " ({{start}}행-끝)", + "linesFromStartTo": " (1-{{end}}행)", + "definitionsOnly": " (정의만)", + "maxLines": " (최대 {{max}}행)" + } +} diff --git a/src/i18n/locales/pl/tools.json b/src/i18n/locales/pl/tools.json new file mode 100644 index 0000000000..33edb77cfa --- /dev/null +++ b/src/i18n/locales/pl/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (linie {{start}}-{{end}})", + "linesFromToEnd": " (linie {{start}}-koniec)", + "linesFromStartTo": " (linie 1-{{end}})", + "definitionsOnly": " (tylko definicje)", + "maxLines": " (maks. {{max}} linii)" + } +} diff --git a/src/i18n/locales/pt-BR/tools.json b/src/i18n/locales/pt-BR/tools.json new file mode 100644 index 0000000000..0992809bdd --- /dev/null +++ b/src/i18n/locales/pt-BR/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (linhas {{start}}-{{end}})", + "linesFromToEnd": " (linhas {{start}}-fim)", + "linesFromStartTo": " (linhas 1-{{end}})", + "definitionsOnly": " (apenas definições)", + "maxLines": " (máx. {{max}} linhas)" + } +} diff --git a/src/i18n/locales/tr/tools.json b/src/i18n/locales/tr/tools.json new file mode 100644 index 0000000000..19b0158d13 --- /dev/null +++ b/src/i18n/locales/tr/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (satır {{start}}-{{end}})", + "linesFromToEnd": " (satır {{start}}-son)", + "linesFromStartTo": " (satır 1-{{end}})", + "definitionsOnly": " (sadece tanımlar)", + "maxLines": " (maks. {{max}} satır)" + } +} diff --git a/src/i18n/locales/vi/tools.json b/src/i18n/locales/vi/tools.json new file mode 100644 index 0000000000..76af39abe1 --- /dev/null +++ b/src/i18n/locales/vi/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (dòng {{start}}-{{end}})", + "linesFromToEnd": " (dòng {{start}}-cuối)", + "linesFromStartTo": " (dòng 1-{{end}})", + "definitionsOnly": " (chỉ định nghĩa)", + "maxLines": " (tối đa {{max}} dòng)" + } +} diff --git a/src/i18n/locales/zh-CN/tools.json b/src/i18n/locales/zh-CN/tools.json new file mode 100644 index 0000000000..5f9b2ddaf7 --- /dev/null +++ b/src/i18n/locales/zh-CN/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (第 {{start}}-{{end}} 行)", + "linesFromToEnd": " (第 {{start}} 行至末尾)", + "linesFromStartTo": " (第 1-{{end}} 行)", + "definitionsOnly": " (仅定义)", + "maxLines": " (最多 {{max}} 行)" + } +} diff --git a/src/i18n/locales/zh-TW/tools.json b/src/i18n/locales/zh-TW/tools.json new file mode 100644 index 0000000000..a9297e876b --- /dev/null +++ b/src/i18n/locales/zh-TW/tools.json @@ -0,0 +1,9 @@ +{ + "readFile": { + "linesRange": " (第 {{start}}-{{end}} 行)", + "linesFromToEnd": " (第 {{start}} 行至末尾)", + "linesFromStartTo": " (第 1-{{end}} 行)", + "definitionsOnly": " (僅定義)", + "maxLines": " (最多 {{max}} 行)" + } +} diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index f1a7ed994b..7fe200c3e9 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -356,6 +356,7 @@ export const ChatRowContent = ({ textAlign: "left", }}> {removeLeadingNonAlphanumeric(tool.path ?? "") + "\u200E"} + {tool.reason}
Date: Sat, 29 Mar 2025 15:49:32 -0400 Subject: [PATCH 10/38] Update contributors list (#2023) docs: update contributors list [skip ci] Co-authored-by: mrubens --- README.md | 36 ++++++++++++++++++------------------ locales/ca/README.md | 22 +++++++++++----------- locales/de/README.md | 22 +++++++++++----------- locales/es/README.md | 22 +++++++++++----------- locales/fr/README.md | 22 +++++++++++----------- locales/hi/README.md | 22 +++++++++++----------- locales/it/README.md | 22 +++++++++++----------- locales/ja/README.md | 22 +++++++++++----------- locales/ko/README.md | 22 +++++++++++----------- locales/pl/README.md | 22 +++++++++++----------- locales/pt-BR/README.md | 22 +++++++++++----------- locales/tr/README.md | 22 +++++++++++----------- locales/vi/README.md | 22 +++++++++++----------- locales/zh-CN/README.md | 22 +++++++++++----------- locales/zh-TW/README.md | 22 +++++++++++----------- 15 files changed, 172 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index 69ffef2f5d..f7691f192a 100644 --- a/README.md +++ b/README.md @@ -180,24 +180,24 @@ Thanks to all our contributors who have helped make Roo Code better! -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| jquanton
jquanton
| nissa-seru
nissa-seru
| -| NyxJae
NyxJae
| hannesrudolph
hannesrudolph
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| vigneshsubbiah16
vigneshsubbiah16
| lloydchang
lloydchang
| cannuri
cannuri
| Szpadel
Szpadel
| psv2522
psv2522
| Premshay
Premshay
| -| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| wkordalski
wkordalski
| qdaxb
qdaxb
| feifei325
feifei325
| lupuletic
lupuletic
| KJ7LNW
KJ7LNW
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| pugazhendhi-m
pugazhendhi-m
| pdecat
pdecat
| emshvac
emshvac
| afshawnlotfi
afshawnlotfi
| -| aitoroses
aitoroses
| dtrugman
dtrugman
| diarmidmackenzie
diarmidmackenzie
| sammcj
sammcj
| aheizi
aheizi
| Lunchb0ne
Lunchb0ne
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| AMHesch
AMHesch
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| alarno
alarno
| -| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| linegel
linegel
| -| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| hesara
hesara
| eltociear
eltociear
| Jdo300
Jdo300
| -| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| | | | | +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| +| hannesrudolph
hannesrudolph
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| +| cannuri
cannuri
| lloydchang
lloydchang
| vigneshsubbiah16
vigneshsubbiah16
| KJ7LNW
KJ7LNW
| Szpadel
Szpadel
| lupuletic
lupuletic
| +| feifei325
feifei325
| qdaxb
qdaxb
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| Premshay
Premshay
| psv2522
psv2522
| +| olweraltuve
olweraltuve
| RaySinner
RaySinner
| aheizi
aheizi
| afshawnlotfi
afshawnlotfi
| diarmidmackenzie
diarmidmackenzie
| emshvac
emshvac
| +| pdecat
pdecat
| pugazhendhi-m
pugazhendhi-m
| Lunchb0ne
Lunchb0ne
| sammcj
sammcj
| dtrugman
dtrugman
| aitoroses
aitoroses
| +| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| +| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| +| mdp
mdp
| napter
napter
| philfung
philfung
| tgfjt
tgfjt
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| +| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| +| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| +| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| +| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| AMHesch
AMHesch
| adamwlarson
adamwlarson
| +| alarno
alarno
| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| +| linegel
linegel
| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| gtaylor
gtaylor
| hesara
hesara
| +| eltociear
eltociear
| Jdo300
Jdo300
| | | | | diff --git a/locales/ca/README.md b/locales/ca/README.md index dc5a706972..935c0962e9 100644 --- a/locales/ca/README.md +++ b/locales/ca/README.md @@ -180,22 +180,22 @@ Gràcies a tots els nostres col·laboradors que han ajudat a millorar Roo Code! |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## Llicència diff --git a/locales/de/README.md b/locales/de/README.md index 4b4880ed07..e5d385c4ef 100644 --- a/locales/de/README.md +++ b/locales/de/README.md @@ -180,22 +180,22 @@ Danke an alle unsere Mitwirkenden, die geholfen haben, Roo Code zu verbessern! |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## Lizenz diff --git a/locales/es/README.md b/locales/es/README.md index be9c015c6b..28d0f5dc01 100644 --- a/locales/es/README.md +++ b/locales/es/README.md @@ -180,22 +180,22 @@ Usamos [changesets](https://github.com/changesets/changesets) para versionar y p |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## Licencia diff --git a/locales/fr/README.md b/locales/fr/README.md index b5810092ac..e7b4b5eea3 100644 --- a/locales/fr/README.md +++ b/locales/fr/README.md @@ -180,22 +180,22 @@ Merci à tous nos contributeurs qui ont aidé à améliorer Roo Code ! |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## Licence diff --git a/locales/hi/README.md b/locales/hi/README.md index 40eeb84b47..ff13e88392 100644 --- a/locales/hi/README.md +++ b/locales/hi/README.md @@ -180,22 +180,22 @@ Roo Code को बेहतर बनाने में मदद करने |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## लाइसेंस diff --git a/locales/it/README.md b/locales/it/README.md index f8da940c3c..5d8b065b52 100644 --- a/locales/it/README.md +++ b/locales/it/README.md @@ -180,22 +180,22 @@ Grazie a tutti i nostri contributori che hanno aiutato a migliorare Roo Code! |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## Licenza diff --git a/locales/ja/README.md b/locales/ja/README.md index cf548895f8..e2ab0212e8 100644 --- a/locales/ja/README.md +++ b/locales/ja/README.md @@ -180,22 +180,22 @@ Roo Codeの改善に貢献してくれたすべての貢献者に感謝します |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## ライセンス diff --git a/locales/ko/README.md b/locales/ko/README.md index cc6c7d3a8e..77c5ef6bee 100644 --- a/locales/ko/README.md +++ b/locales/ko/README.md @@ -180,22 +180,22 @@ Roo Code를 더 좋게 만드는 데 도움을 준 모든 기여자에게 감사 |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## 라이선스 diff --git a/locales/pl/README.md b/locales/pl/README.md index a8b18e4691..cf7671ab2d 100644 --- a/locales/pl/README.md +++ b/locales/pl/README.md @@ -180,22 +180,22 @@ Dziękujemy wszystkim naszym współtwórcom, którzy pomogli ulepszyć Roo Code |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## Licencja diff --git a/locales/pt-BR/README.md b/locales/pt-BR/README.md index faecb8d379..397ba9f625 100644 --- a/locales/pt-BR/README.md +++ b/locales/pt-BR/README.md @@ -180,22 +180,22 @@ Obrigado a todos os nossos contribuidores que ajudaram a tornar o Roo Code melho |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## Licença diff --git a/locales/tr/README.md b/locales/tr/README.md index 0ca3ec5f7f..1b1fa3c099 100644 --- a/locales/tr/README.md +++ b/locales/tr/README.md @@ -180,22 +180,22 @@ Roo Code'u daha iyi hale getirmeye yardımcı olan tüm katkıda bulunanlara te |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## Lisans diff --git a/locales/vi/README.md b/locales/vi/README.md index 78f7476eef..d0e4697eaf 100644 --- a/locales/vi/README.md +++ b/locales/vi/README.md @@ -180,22 +180,22 @@ Cảm ơn tất cả những người đóng góp đã giúp cải thiện Roo C |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## Giấy Phép diff --git a/locales/zh-CN/README.md b/locales/zh-CN/README.md index 27e1213bf8..c3a7d3076b 100644 --- a/locales/zh-CN/README.md +++ b/locales/zh-CN/README.md @@ -180,22 +180,22 @@ code --install-extension bin/roo-cline-.vsix |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## 许可证 diff --git a/locales/zh-TW/README.md b/locales/zh-TW/README.md index 310a89e304..8643ed1de6 100644 --- a/locales/zh-TW/README.md +++ b/locales/zh-TW/README.md @@ -180,22 +180,22 @@ code --install-extension bin/roo-cline-.vsix |mrubens
mrubens
|saoudrizwan
saoudrizwan
|cte
cte
|samhvw8
samhvw8
|daniel-lxs
daniel-lxs
|a8trejo
a8trejo
| |:---:|:---:|:---:|:---:|:---:|:---:| -|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|jquanton
jquanton
|nissa-seru
nissa-seru
| -|NyxJae
NyxJae
|hannesrudolph
hannesrudolph
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| -|vigneshsubbiah16
vigneshsubbiah16
|lloydchang
lloydchang
|cannuri
cannuri
|Szpadel
Szpadel
|psv2522
psv2522
|Premshay
Premshay
| -|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|wkordalski
wkordalski
|qdaxb
qdaxb
|feifei325
feifei325
|lupuletic
lupuletic
|KJ7LNW
KJ7LNW
| -|olweraltuve
olweraltuve
|RaySinner
RaySinner
|pugazhendhi-m
pugazhendhi-m
|pdecat
pdecat
|emshvac
emshvac
|afshawnlotfi
afshawnlotfi
| -|aitoroses
aitoroses
|dtrugman
dtrugman
|diarmidmackenzie
diarmidmackenzie
|sammcj
sammcj
|aheizi
aheizi
|Lunchb0ne
Lunchb0ne
| +|ColemanRoo
ColemanRoo
|stea9499
stea9499
|joemanley201
joemanley201
|System233
System233
|nissa-seru
nissa-seru
|jquanton
jquanton
| +|hannesrudolph
hannesrudolph
|NyxJae
NyxJae
|MuriloFP
MuriloFP
|punkpeye
punkpeye
|d-oit
d-oit
|monotykamary
monotykamary
| +|cannuri
cannuri
|lloydchang
lloydchang
|vigneshsubbiah16
vigneshsubbiah16
|KJ7LNW
KJ7LNW
|Szpadel
Szpadel
|lupuletic
lupuletic
| +|feifei325
feifei325
|qdaxb
qdaxb
|wkordalski
wkordalski
|Smartsheet-JB-Brown
Smartsheet-JB-Brown
|Premshay
Premshay
|psv2522
psv2522
| +|olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| +|pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| |heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|AMHesch
AMHesch
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| +|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| |ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| |kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| |oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
|alarno
alarno
| -|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
|linegel
linegel
| -|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|hesara
hesara
|eltociear
eltociear
|Jdo300
Jdo300
| -|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
| | | | | +|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| +|linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| +|eltociear
eltociear
|Jdo300
Jdo300
| | | | | ## 許可證 From 40a0766161cde8ec961053800a4925709dd9ce06 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 00:08:48 -0400 Subject: [PATCH 11/38] Handle multiple API pages worth of contributors (#2088) --- scripts/update-contributors.js | 270 +++++++++++++++++++++------------ 1 file changed, 177 insertions(+), 93 deletions(-) diff --git a/scripts/update-contributors.js b/scripts/update-contributors.js index b737ee5f0d..ab369853cf 100755 --- a/scripts/update-contributors.js +++ b/scripts/update-contributors.js @@ -8,8 +8,13 @@ const https = require("https") const fs = require("fs") +const { promisify } = require("util") const path = require("path") +// Promisify filesystem operations +const readFileAsync = promisify(fs.readFile) +const writeFileAsync = promisify(fs.writeFile) + // GitHub API URL for fetching contributors const GITHUB_API_URL = "https://api.github.com/repos/RooVetGit/Roo-Code/contributors?per_page=100" const README_PATH = path.join(__dirname, "..", "README.md") @@ -33,52 +38,144 @@ if (process.env.GITHUB_TOKEN) { } /** - * Fetches contributors data from GitHub API - * @returns {Promise} Array of contributor objects + * Parses the GitHub API Link header to extract pagination URLs + * Based on RFC 5988 format for the Link header + * @param {string} header The Link header from GitHub API response + * @returns {Object} Object containing URLs for next, prev, first, last pages (if available) */ -function fetchContributors() { +function parseLinkHeader(header) { + // Return empty object if no header is provided + if (!header || header.trim() === "") return {} + + // Initialize links object + const links = {} + + // Split the header into individual link entries + // Example: ; rel="next", ; rel="last" + const entries = header.split(/,\s*/) + + // Process each link entry + for (const entry of entries) { + // Extract the URL (between < and >) and the parameters (after >) + const segments = entry.split(";") + if (segments.length < 2) continue + + // Extract URL from the first segment, removing < and > + const urlMatch = segments[0].match(/<(.+)>/) + if (!urlMatch) continue + const url = urlMatch[1] + + // Find the rel="value" parameter + let rel = null + for (let i = 1; i < segments.length; i++) { + const relMatch = segments[i].match(/\s*rel\s*=\s*"?([^"]+)"?/) + if (relMatch) { + rel = relMatch[1] + break + } + } + + // Only add to links if both URL and rel were found + if (rel) { + links[rel] = url + } + } + + return links +} + +/** + * Performs an HTTP GET request and returns the response + * @param {string} url The URL to fetch + * @param {Object} options Request options + * @returns {Promise} Response object with status, headers and body + */ +function httpGet(url, options) { return new Promise((resolve, reject) => { https - .get(GITHUB_API_URL, options, (res) => { - if (res.statusCode !== 200) { - reject(new Error(`GitHub API request failed with status code: ${res.statusCode}`)) - return - } - + .get(url, options, (res) => { let data = "" res.on("data", (chunk) => { data += chunk }) res.on("end", () => { - try { - const contributors = JSON.parse(data) - resolve(contributors) - } catch (error) { - reject(new Error(`Failed to parse GitHub API response: ${error.message}`)) - } + resolve({ + statusCode: res.statusCode, + headers: res.headers, + body: data, + }) }) }) .on("error", (error) => { - reject(new Error(`GitHub API request failed: ${error.message}`)) + reject(error) }) }) } +/** + * Fetches a single page of contributors from GitHub API + * @param {string} url The API URL to fetch + * @returns {Promise} Object containing contributors and pagination links + */ +async function fetchContributorsPage(url) { + try { + // Make the HTTP request + const response = await httpGet(url, options) + + // Check for successful response + if (response.statusCode !== 200) { + throw new Error(`GitHub API request failed with status code: ${response.statusCode}`) + } + + // Parse the Link header for pagination + const linkHeader = response.headers.link + const links = parseLinkHeader(linkHeader) + + // Parse the JSON response + const contributors = JSON.parse(response.body) + + return { contributors, links } + } catch (error) { + throw new Error(`Failed to fetch contributors page: ${error.message}`) + } +} + +/** + * Fetches all contributors data from GitHub API (handling pagination) + * @returns {Promise} Array of all contributor objects + */ +async function fetchContributors() { + let allContributors = [] + let currentUrl = GITHUB_API_URL + let pageCount = 1 + + // Loop through all pages of contributors + while (currentUrl) { + console.log(`Fetching contributors page ${pageCount}...`) + const { contributors, links } = await fetchContributorsPage(currentUrl) + + allContributors = allContributors.concat(contributors) + + // Move to the next page if it exists + currentUrl = links.next + pageCount++ + } + + console.log(`Fetched ${allContributors.length} contributors from ${pageCount - 1} pages`) + return allContributors +} + /** * Reads the README.md file * @returns {Promise} README content */ -function readReadme() { - return new Promise((resolve, reject) => { - fs.readFile(README_PATH, "utf8", (err, data) => { - if (err) { - reject(new Error(`Failed to read README.md: ${err.message}`)) - return - } - resolve(data) - }) - }) +async function readReadme() { + try { + return await readFileAsync(README_PATH, "utf8") + } catch (err) { + throw new Error(`Failed to read README.md: ${err.message}`) + } } /** @@ -147,7 +244,7 @@ function formatContributorsSection(contributors) { * @param {string} contributorsSection HTML for contributors section * @returns {Promise} */ -function updateReadme(readmeContent, contributorsSection) { +async function updateReadme(readmeContent, contributorsSection) { // Find existing contributors section markers const startPos = readmeContent.indexOf(START_MARKER) const endPos = readmeContent.indexOf(END_MARKER) @@ -164,7 +261,7 @@ function updateReadme(readmeContent, contributorsSection) { // Ensure single newline separators between sections const updatedContent = beforeSection + "\n\n" + contributorsSection.trim() + "\n\n" + afterSection - return writeReadme(updatedContent) + await writeReadme(updatedContent) } /** @@ -172,47 +269,41 @@ function updateReadme(readmeContent, contributorsSection) { * @param {string} content Updated README content * @returns {Promise} */ -function writeReadme(content) { - return new Promise((resolve, reject) => { - fs.writeFile(README_PATH, content, "utf8", (err) => { - if (err) { - reject(new Error(`Failed to write updated README.md: ${err.message}`)) - return - } - resolve() - }) - }) +async function writeReadme(content) { + try { + await writeFileAsync(README_PATH, content, "utf8") + } catch (err) { + throw new Error(`Failed to write updated README.md: ${err.message}`) + } } /** * Finds all localized README files in the locales directory * @returns {Promise} Array of README file paths */ -function findLocalizedReadmes() { - return new Promise((resolve) => { - const readmeFiles = [] +async function findLocalizedReadmes() { + const readmeFiles = [] - // Check if locales directory exists - if (!fs.existsSync(LOCALES_DIR)) { - // No localized READMEs found - return resolve(readmeFiles) + // Check if locales directory exists + if (!fs.existsSync(LOCALES_DIR)) { + // No localized READMEs found + return readmeFiles + } + + // Get all language subdirectories + const languageDirs = fs + .readdirSync(LOCALES_DIR, { withFileTypes: true }) + .filter((dirent) => dirent.isDirectory()) + .map((dirent) => dirent.name) + + // Add all localized READMEs to the list + for (const langDir of languageDirs) { + const readmePath = path.join(LOCALES_DIR, langDir, "README.md") + if (fs.existsSync(readmePath)) { + readmeFiles.push(readmePath) } + } - // Get all language subdirectories - const languageDirs = fs - .readdirSync(LOCALES_DIR, { withFileTypes: true }) - .filter((dirent) => dirent.isDirectory()) - .map((dirent) => dirent.name) - - // Add all localized READMEs to the list - for (const langDir of languageDirs) { - const readmePath = path.join(LOCALES_DIR, langDir, "README.md") - if (fs.existsSync(readmePath)) { - readmeFiles.push(readmePath) - } - } - - resolve(readmeFiles) - }) + return readmeFiles } /** @@ -221,40 +312,33 @@ function findLocalizedReadmes() { * @param {string} contributorsSection HTML for contributors section * @returns {Promise} */ -function updateLocalizedReadme(filePath, contributorsSection) { - return new Promise((resolve, reject) => { - fs.readFile(filePath, "utf8", (err, readmeContent) => { - if (err) { - console.warn(`Warning: Could not read ${filePath}: ${err.message}`) - return resolve() - } +async function updateLocalizedReadme(filePath, contributorsSection) { + try { + // Read the file content + const readmeContent = await readFileAsync(filePath, "utf8") - // Find existing contributors section markers - const startPos = readmeContent.indexOf(START_MARKER) - const endPos = readmeContent.indexOf(END_MARKER) + // Find existing contributors section markers + const startPos = readmeContent.indexOf(START_MARKER) + const endPos = readmeContent.indexOf(END_MARKER) - if (startPos === -1 || endPos === -1) { - console.warn(`Warning: Could not find contributors section markers in ${filePath}`) - console.warn(`Skipping update for ${filePath}`) - return resolve() - } + if (startPos === -1 || endPos === -1) { + console.warn(`Warning: Could not find contributors section markers in ${filePath}`) + console.warn(`Skipping update for ${filePath}`) + return + } - // Replace existing section, trimming whitespace at section boundaries - const beforeSection = readmeContent.substring(0, startPos).trimEnd() - const afterSection = readmeContent.substring(endPos + END_MARKER.length).trimStart() - // Ensure single newline separators between sections - const updatedContent = beforeSection + "\n\n" + contributorsSection.trim() + "\n\n" + afterSection + // Replace existing section, trimming whitespace at section boundaries + const beforeSection = readmeContent.substring(0, startPos).trimEnd() + const afterSection = readmeContent.substring(endPos + END_MARKER.length).trimStart() + // Ensure single newline separators between sections + const updatedContent = beforeSection + "\n\n" + contributorsSection.trim() + "\n\n" + afterSection - fs.writeFile(filePath, updatedContent, "utf8", (writeErr) => { - if (writeErr) { - console.warn(`Warning: Failed to update ${filePath}: ${writeErr.message}`) - return resolve() - } - console.log(`Updated ${filePath}`) - resolve() - }) - }) - }) + // Write the updated content + await writeFileAsync(filePath, updatedContent, "utf8") + console.log(`Updated ${filePath}`) + } catch (err) { + console.warn(`Warning: Could not update ${filePath}: ${err.message}`) + } } /** @@ -262,9 +346,9 @@ function updateLocalizedReadme(filePath, contributorsSection) { */ async function main() { try { - // Fetch contributors from GitHub + // Fetch contributors from GitHub (now handles pagination) const contributors = await fetchContributors() - console.log(`Fetched ${contributors.length} contributors from GitHub`) + console.log(`Total contributors: ${contributors.length}`) // Generate contributors section const contributorsSection = formatContributorsSection(contributors) From db66e8df4964cf027523eb01136d608370a2da74 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 30 Mar 2025 00:10:51 -0400 Subject: [PATCH 12/38] Update contributors list (#2089) docs: update contributors list [skip ci] Co-authored-by: mrubens --- README.md | 38 ++++++++++++++++++++------------------ locales/ca/README.md | 16 +++++++++------- locales/de/README.md | 16 +++++++++------- locales/es/README.md | 16 +++++++++------- locales/fr/README.md | 16 +++++++++------- locales/hi/README.md | 16 +++++++++------- locales/it/README.md | 16 +++++++++------- locales/ja/README.md | 16 +++++++++------- locales/ko/README.md | 16 +++++++++------- locales/pl/README.md | 16 +++++++++------- locales/pt-BR/README.md | 16 +++++++++------- locales/tr/README.md | 16 +++++++++------- locales/vi/README.md | 16 +++++++++------- locales/zh-CN/README.md | 16 +++++++++------- locales/zh-TW/README.md | 16 +++++++++------- 15 files changed, 146 insertions(+), 116 deletions(-) diff --git a/README.md b/README.md index f7691f192a..47709fab60 100644 --- a/README.md +++ b/README.md @@ -180,24 +180,26 @@ Thanks to all our contributors who have helped make Roo Code better! -| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| -| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | -| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| -| hannesrudolph
hannesrudolph
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| -| cannuri
cannuri
| lloydchang
lloydchang
| vigneshsubbiah16
vigneshsubbiah16
| KJ7LNW
KJ7LNW
| Szpadel
Szpadel
| lupuletic
lupuletic
| -| feifei325
feifei325
| qdaxb
qdaxb
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| Premshay
Premshay
| psv2522
psv2522
| -| olweraltuve
olweraltuve
| RaySinner
RaySinner
| aheizi
aheizi
| afshawnlotfi
afshawnlotfi
| diarmidmackenzie
diarmidmackenzie
| emshvac
emshvac
| -| pdecat
pdecat
| pugazhendhi-m
pugazhendhi-m
| Lunchb0ne
Lunchb0ne
| sammcj
sammcj
| dtrugman
dtrugman
| aitoroses
aitoroses
| -| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| -| heyseth
heyseth
| anton-otee
anton-otee
| benzntech
benzntech
| GitlyHallows
GitlyHallows
| jcbdev
jcbdev
| Chenjiayuan195
Chenjiayuan195
| -| mdp
mdp
| napter
napter
| philfung
philfung
| tgfjt
tgfjt
| vladstudio
vladstudio
| Yoshino-Yukitaro
Yoshino-Yukitaro
| -| ashktn
ashktn
| bannzai
bannzai
| dairui1
dairui1
| dqroid
dqroid
| im47cn
im47cn
| jwcraig
jwcraig
| -| kinandan
kinandan
| kohii
kohii
| lightrabbit
lightrabbit
| olup
olup
| moqimoqidea
moqimoqidea
| mosleyit
mosleyit
| -| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| shohei-ihaya
shohei-ihaya
| -| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| AMHesch
AMHesch
| adamwlarson
adamwlarson
| -| alarno
alarno
| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| -| linegel
linegel
| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| gtaylor
gtaylor
| hesara
hesara
| -| eltociear
eltociear
| Jdo300
Jdo300
| | | | | +| mrubens
mrubens
| saoudrizwan
saoudrizwan
| cte
cte
| samhvw8
samhvw8
| daniel-lxs
daniel-lxs
| a8trejo
a8trejo
| +| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | +| ColemanRoo
ColemanRoo
| stea9499
stea9499
| joemanley201
joemanley201
| System233
System233
| nissa-seru
nissa-seru
| jquanton
jquanton
| +| hannesrudolph
hannesrudolph
| NyxJae
NyxJae
| MuriloFP
MuriloFP
| punkpeye
punkpeye
| d-oit
d-oit
| monotykamary
monotykamary
| +| cannuri
cannuri
| lloydchang
lloydchang
| vigneshsubbiah16
vigneshsubbiah16
| KJ7LNW
KJ7LNW
| Szpadel
Szpadel
| lupuletic
lupuletic
| +| feifei325
feifei325
| qdaxb
qdaxb
| wkordalski
wkordalski
| Smartsheet-JB-Brown
Smartsheet-JB-Brown
| Premshay
Premshay
| psv2522
psv2522
| +| olweraltuve
olweraltuve
| RaySinner
RaySinner
| aheizi
aheizi
| afshawnlotfi
afshawnlotfi
| diarmidmackenzie
diarmidmackenzie
| emshvac
emshvac
| +| pdecat
pdecat
| pugazhendhi-m
pugazhendhi-m
| Lunchb0ne
Lunchb0ne
| sammcj
sammcj
| dtrugman
dtrugman
| aitoroses
aitoroses
| +| yt3trees
yt3trees
| yongjer
yongjer
| vincentsong
vincentsong
| teddyOOXX
teddyOOXX
| eonghk
eonghk
| arthurauffray
arthurauffray
| +| heyseth
heyseth
| philfung
philfung
| napter
napter
| mdp
mdp
| Chenjiayuan195
Chenjiayuan195
| jcbdev
jcbdev
| +| GitlyHallows
GitlyHallows
| benzntech
benzntech
| anton-otee
anton-otee
| lightrabbit
lightrabbit
| kohii
kohii
| kinandan
kinandan
| +| jwcraig
jwcraig
| im47cn
im47cn
| dqroid
dqroid
| dairui1
dairui1
| bannzai
bannzai
| ashktn
ashktn
| +| Yoshino-Yukitaro
Yoshino-Yukitaro
| vladstudio
vladstudio
| tgfjt
tgfjt
| AMHesch
AMHesch
| olup
olup
| moqimoqidea
moqimoqidea
| +| mosleyit
mosleyit
| oprstchn
oprstchn
| philipnext
philipnext
| refactorthis
refactorthis
| samir-nimbly
samir-nimbly
| shaybc
shaybc
| +| shohei-ihaya
shohei-ihaya
| student20880
student20880
| cdlliuy
cdlliuy
| PretzelVector
PretzelVector
| nevermorec
nevermorec
| adamwlarson
adamwlarson
| +| alarno
alarno
| andreastempsch
andreastempsch
| Atlogit
Atlogit
| chadgauth
chadgauth
| dleen
dleen
| dbasclpy
dbasclpy
| +| linegel
linegel
| celestial-vault
celestial-vault
| franekp
franekp
| DeXtroTip
DeXtroTip
| gtaylor
gtaylor
| hesara
hesara
| +| eltociear
eltociear
| Jdo300
Jdo300
| libertyteeth
libertyteeth
| mamertofabian
mamertofabian
| marvijo-code
marvijo-code
| kvokka
kvokka
| +| nbihan-mediware
nbihan-mediware
| PeterDaveHello
PeterDaveHello
| Sarke
Sarke
| 01Rian
01Rian
| samsilveira
samsilveira
| StevenTCramer
StevenTCramer
| +| maekawataiki
maekawataiki
| | | | | | diff --git a/locales/ca/README.md b/locales/ca/README.md index 935c0962e9..1ae4abf0c4 100644 --- a/locales/ca/README.md +++ b/locales/ca/README.md @@ -187,15 +187,17 @@ Gràcies a tots els nostres col·laboradors que han ajudat a millorar Roo Code! |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Llicència diff --git a/locales/de/README.md b/locales/de/README.md index e5d385c4ef..277c9a7e83 100644 --- a/locales/de/README.md +++ b/locales/de/README.md @@ -187,15 +187,17 @@ Danke an alle unsere Mitwirkenden, die geholfen haben, Roo Code zu verbessern! |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Lizenz diff --git a/locales/es/README.md b/locales/es/README.md index 28d0f5dc01..db368f1221 100644 --- a/locales/es/README.md +++ b/locales/es/README.md @@ -187,15 +187,17 @@ Usamos [changesets](https://github.com/changesets/changesets) para versionar y p |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Licencia diff --git a/locales/fr/README.md b/locales/fr/README.md index e7b4b5eea3..16421964f0 100644 --- a/locales/fr/README.md +++ b/locales/fr/README.md @@ -187,15 +187,17 @@ Merci à tous nos contributeurs qui ont aidé à améliorer Roo Code ! |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Licence diff --git a/locales/hi/README.md b/locales/hi/README.md index ff13e88392..fc2fb0b2c9 100644 --- a/locales/hi/README.md +++ b/locales/hi/README.md @@ -187,15 +187,17 @@ Roo Code को बेहतर बनाने में मदद करने |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## लाइसेंस diff --git a/locales/it/README.md b/locales/it/README.md index 5d8b065b52..6ec0af37b6 100644 --- a/locales/it/README.md +++ b/locales/it/README.md @@ -187,15 +187,17 @@ Grazie a tutti i nostri contributori che hanno aiutato a migliorare Roo Code! |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Licenza diff --git a/locales/ja/README.md b/locales/ja/README.md index e2ab0212e8..e8c24ceb9c 100644 --- a/locales/ja/README.md +++ b/locales/ja/README.md @@ -187,15 +187,17 @@ Roo Codeの改善に貢献してくれたすべての貢献者に感謝します |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## ライセンス diff --git a/locales/ko/README.md b/locales/ko/README.md index 77c5ef6bee..19c6c57fe5 100644 --- a/locales/ko/README.md +++ b/locales/ko/README.md @@ -187,15 +187,17 @@ Roo Code를 더 좋게 만드는 데 도움을 준 모든 기여자에게 감사 |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## 라이선스 diff --git a/locales/pl/README.md b/locales/pl/README.md index cf7671ab2d..5afcf5a401 100644 --- a/locales/pl/README.md +++ b/locales/pl/README.md @@ -187,15 +187,17 @@ Dziękujemy wszystkim naszym współtwórcom, którzy pomogli ulepszyć Roo Code |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Licencja diff --git a/locales/pt-BR/README.md b/locales/pt-BR/README.md index 397ba9f625..eb8b164096 100644 --- a/locales/pt-BR/README.md +++ b/locales/pt-BR/README.md @@ -187,15 +187,17 @@ Obrigado a todos os nossos contribuidores que ajudaram a tornar o Roo Code melho |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Licença diff --git a/locales/tr/README.md b/locales/tr/README.md index 1b1fa3c099..24b5dd679e 100644 --- a/locales/tr/README.md +++ b/locales/tr/README.md @@ -187,15 +187,17 @@ Roo Code'u daha iyi hale getirmeye yardımcı olan tüm katkıda bulunanlara te |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Lisans diff --git a/locales/vi/README.md b/locales/vi/README.md index d0e4697eaf..e15a3063b8 100644 --- a/locales/vi/README.md +++ b/locales/vi/README.md @@ -187,15 +187,17 @@ Cảm ơn tất cả những người đóng góp đã giúp cải thiện Roo C |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## Giấy Phép diff --git a/locales/zh-CN/README.md b/locales/zh-CN/README.md index c3a7d3076b..cad76e3ddf 100644 --- a/locales/zh-CN/README.md +++ b/locales/zh-CN/README.md @@ -187,15 +187,17 @@ code --install-extension bin/roo-cline-.vsix |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## 许可证 diff --git a/locales/zh-TW/README.md b/locales/zh-TW/README.md index 8643ed1de6..bc34f94c8d 100644 --- a/locales/zh-TW/README.md +++ b/locales/zh-TW/README.md @@ -187,15 +187,17 @@ code --install-extension bin/roo-cline-.vsix |olweraltuve
olweraltuve
|RaySinner
RaySinner
|aheizi
aheizi
|afshawnlotfi
afshawnlotfi
|diarmidmackenzie
diarmidmackenzie
|emshvac
emshvac
| |pdecat
pdecat
|pugazhendhi-m
pugazhendhi-m
|Lunchb0ne
Lunchb0ne
|sammcj
sammcj
|dtrugman
dtrugman
|aitoroses
aitoroses
| |yt3trees
yt3trees
|yongjer
yongjer
|vincentsong
vincentsong
|teddyOOXX
teddyOOXX
|eonghk
eonghk
|arthurauffray
arthurauffray
| -|heyseth
heyseth
|anton-otee
anton-otee
|benzntech
benzntech
|GitlyHallows
GitlyHallows
|jcbdev
jcbdev
|Chenjiayuan195
Chenjiayuan195
| -|mdp
mdp
|napter
napter
|philfung
philfung
|tgfjt
tgfjt
|vladstudio
vladstudio
|Yoshino-Yukitaro
Yoshino-Yukitaro
| -|ashktn
ashktn
|bannzai
bannzai
|dairui1
dairui1
|dqroid
dqroid
|im47cn
im47cn
|jwcraig
jwcraig
| -|kinandan
kinandan
|kohii
kohii
|lightrabbit
lightrabbit
|olup
olup
|moqimoqidea
moqimoqidea
|mosleyit
mosleyit
| -|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
|shohei-ihaya
shohei-ihaya
| -|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|AMHesch
AMHesch
|adamwlarson
adamwlarson
| +|heyseth
heyseth
|philfung
philfung
|napter
napter
|mdp
mdp
|Chenjiayuan195
Chenjiayuan195
|jcbdev
jcbdev
| +|GitlyHallows
GitlyHallows
|benzntech
benzntech
|anton-otee
anton-otee
|lightrabbit
lightrabbit
|kohii
kohii
|kinandan
kinandan
| +|jwcraig
jwcraig
|im47cn
im47cn
|dqroid
dqroid
|dairui1
dairui1
|bannzai
bannzai
|ashktn
ashktn
| +|Yoshino-Yukitaro
Yoshino-Yukitaro
|vladstudio
vladstudio
|tgfjt
tgfjt
|AMHesch
AMHesch
|olup
olup
|moqimoqidea
moqimoqidea
| +|mosleyit
mosleyit
|oprstchn
oprstchn
|philipnext
philipnext
|refactorthis
refactorthis
|samir-nimbly
samir-nimbly
|shaybc
shaybc
| +|shohei-ihaya
shohei-ihaya
|student20880
student20880
|cdlliuy
cdlliuy
|PretzelVector
PretzelVector
|nevermorec
nevermorec
|adamwlarson
adamwlarson
| |alarno
alarno
|andreastempsch
andreastempsch
|Atlogit
Atlogit
|chadgauth
chadgauth
|dleen
dleen
|dbasclpy
dbasclpy
| |linegel
linegel
|celestial-vault
celestial-vault
|franekp
franekp
|DeXtroTip
DeXtroTip
|gtaylor
gtaylor
|hesara
hesara
| -|eltociear
eltociear
|Jdo300
Jdo300
| | | | | +|eltociear
eltociear
|Jdo300
Jdo300
|libertyteeth
libertyteeth
|mamertofabian
mamertofabian
|marvijo-code
marvijo-code
|kvokka
kvokka
| +|nbihan-mediware
nbihan-mediware
|PeterDaveHello
PeterDaveHello
|Sarke
Sarke
|01Rian
01Rian
|samsilveira
samsilveira
|StevenTCramer
StevenTCramer
| +|maekawataiki
maekawataiki
| | | | | | ## 許可證 From e95afc250bf0fe4494366785b0ffd9847a050864 Mon Sep 17 00:00:00 2001 From: Bhavesh Ramburn Date: Sun, 30 Mar 2025 05:20:06 +0100 Subject: [PATCH 13/38] Refactor/cline.ts/list files (#2067) * "Refactor list_files tool to separate module (#2057)" * Await --------- Co-authored-by: Matt Rubens --- src/core/Cline.ts | 53 ++-------------------- src/core/tools/listFilesTool.ts | 78 +++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 49 deletions(-) create mode 100644 src/core/tools/listFilesTool.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index b83a7157cf..142f6bd3db 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -30,6 +30,7 @@ import { } from "../integrations/misc/extract-text" import { countFileLines } from "../integrations/misc/line-counter" import { fetchInstructionsTool } from "./tools/fetchInstructionsTool" +import { listFilesTool } from "./tools/listFilesTool" import { readFileTool } from "./tools/readFileTool" import { ExitCodeDetails } from "../integrations/terminal/TerminalProcess" import { Terminal } from "../integrations/terminal/Terminal" @@ -1516,7 +1517,7 @@ export class Cline extends EventEmitter { } // If block is partial, remove partial closing tag so its not presented to user - const removeClosingTag = (tag: ToolParamName, text?: string) => { + const removeClosingTag = (tag: ToolParamName, text?: string): string => { if (!block.partial) { return text || "" } @@ -2265,54 +2266,8 @@ export class Cline extends EventEmitter { } case "list_files": { - const relDirPath: string | undefined = block.params.path - const recursiveRaw: string | undefined = block.params.recursive - const recursive = recursiveRaw?.toLowerCase() === "true" - const sharedMessageProps: ClineSayTool = { - tool: !recursive ? "listFilesTopLevel" : "listFilesRecursive", - path: getReadablePath(this.cwd, removeClosingTag("path", relDirPath)), - } - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: "", - } satisfies ClineSayTool) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!relDirPath) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("list_files", "path")) - break - } - this.consecutiveMistakeCount = 0 - const absolutePath = path.resolve(this.cwd, relDirPath) - const [files, didHitLimit] = await listFiles(absolutePath, recursive, 200) - const { showRooIgnoredFiles = true } = - (await this.providerRef.deref()?.getState()) ?? {} - const result = formatResponse.formatFilesList( - absolutePath, - files, - didHitLimit, - this.rooIgnoreController, - showRooIgnoredFiles, - ) - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: result, - } satisfies ClineSayTool) - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - pushToolResult(result) - break - } - } catch (error) { - await handleError("listing files", error) - break - } + await listFilesTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break } case "list_code_definition_names": { const relPath: string | undefined = block.params.path diff --git a/src/core/tools/listFilesTool.ts b/src/core/tools/listFilesTool.ts new file mode 100644 index 0000000000..879cc7b6df --- /dev/null +++ b/src/core/tools/listFilesTool.ts @@ -0,0 +1,78 @@ +import * as path from "path" +import { Cline } from "../Cline" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { ToolParamName, ToolUse } from "../assistant-message" +import { formatResponse } from "../prompts/responses" +import { listFiles } from "../../services/glob/list-files" +import { getReadablePath } from "../../utils/path" +import { AskApproval, HandleError, PushToolResult } from "./types" +/** + * Implements the list_files tool. + * + * @param cline - The instance of Cline that is executing this tool. + * @param block - The block of assistant message content that specifies the + * parameters for this tool. + * @param askApproval - A function that asks the user for approval to show a + * message. + * @param handleError - A function that handles an error that occurred while + * executing this tool. + * @param pushToolResult - A function that pushes the result of this tool to the + * conversation. + * @param removeClosingTag - A function that removes a closing tag from a string. + */ +export async function listFilesTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: (tag: ToolParamName, text?: string) => string, +) { + const relDirPath: string | undefined = block.params.path + const recursiveRaw: string | undefined = block.params.recursive + const recursive = recursiveRaw?.toLowerCase() === "true" + const sharedMessageProps: ClineSayTool = { + tool: !recursive ? "listFilesTopLevel" : "listFilesRecursive", + path: getReadablePath(cline.cwd, removeClosingTag("path", relDirPath)), + } + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: "", + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!relDirPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("list_files", "path")) + return + } + cline.consecutiveMistakeCount = 0 + const absolutePath = path.resolve(cline.cwd, relDirPath) + const [files, didHitLimit] = await listFiles(absolutePath, recursive, 200) + const { showRooIgnoredFiles = true } = (await cline.providerRef.deref()?.getState()) ?? {} + const result = formatResponse.formatFilesList( + absolutePath, + files, + didHitLimit, + cline.rooIgnoreController, + showRooIgnoredFiles, + ) + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: result, + } satisfies ClineSayTool) + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return + } + pushToolResult(result) + return + } + } catch (error) { + await handleError("listing files", error) + return + } +} From 72894c22a0488b93dd11f150908cfc910c5b5dc8 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 00:31:56 -0400 Subject: [PATCH 14/38] Remove switch from tools (#2091) * HandleError returns a promise * Remove unnecessary switch from tools --- src/core/tools/fetchInstructionsTool.ts | 93 ++++---- src/core/tools/listFilesTool.ts | 2 - src/core/tools/readFileTool.ts | 297 ++++++++++++------------ src/core/tools/types.ts | 2 +- 4 files changed, 191 insertions(+), 203 deletions(-) diff --git a/src/core/tools/fetchInstructionsTool.ts b/src/core/tools/fetchInstructionsTool.ts index 8304c76317..3e185301db 100644 --- a/src/core/tools/fetchInstructionsTool.ts +++ b/src/core/tools/fetchInstructionsTool.ts @@ -12,58 +12,53 @@ export async function fetchInstructionsTool( handleError: HandleError, pushToolResult: PushToolResult, ) { - switch (true) { - default: - const task: string | undefined = block.params.task - const sharedMessageProps: ClineSayTool = { - tool: "fetchInstructions", + const task: string | undefined = block.params.task + const sharedMessageProps: ClineSayTool = { + tool: "fetchInstructions", + content: task, + } + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: undefined, + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!task) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("fetch_instructions", "task")) + return + } + + cline.consecutiveMistakeCount = 0 + const completeMessage = JSON.stringify({ + ...sharedMessageProps, content: task, + } satisfies ClineSayTool) + + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return } - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: undefined, - } satisfies ClineSayTool) - await cline.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!task) { - cline.consecutiveMistakeCount++ - pushToolResult(await cline.sayAndCreateMissingParamError("fetch_instructions", "task")) - break - } - cline.consecutiveMistakeCount = 0 - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: task, - } satisfies ClineSayTool) - - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - - // now fetch the content and provide it to the agent. - const provider = cline.providerRef.deref() - const mcpHub = provider?.getMcpHub() - if (!mcpHub) { - throw new Error("MCP hub not available") - } - const diffStrategy = cline.diffStrategy - const context = provider?.context - const content = await fetchInstructions(task, { mcpHub, diffStrategy, context }) - if (!content) { - pushToolResult(formatResponse.toolError(`Invalid instructions request: ${task}`)) - break - } - pushToolResult(content) - break - } - } catch (error) { - await handleError("fetch instructions", error) - break + // now fetch the content and provide it to the agent. + const provider = cline.providerRef.deref() + const mcpHub = provider?.getMcpHub() + if (!mcpHub) { + throw new Error("MCP hub not available") } + const diffStrategy = cline.diffStrategy + const context = provider?.context + const content = await fetchInstructions(task, { mcpHub, diffStrategy, context }) + if (!content) { + pushToolResult(formatResponse.toolError(`Invalid instructions request: ${task}`)) + return + } + pushToolResult(content) + } + } catch (error) { + await handleError("fetch instructions", error) } } diff --git a/src/core/tools/listFilesTool.ts b/src/core/tools/listFilesTool.ts index 879cc7b6df..838d9886f1 100644 --- a/src/core/tools/listFilesTool.ts +++ b/src/core/tools/listFilesTool.ts @@ -69,10 +69,8 @@ export async function listFilesTool( return } pushToolResult(result) - return } } catch (error) { await handleError("listing files", error) - return } } diff --git a/src/core/tools/readFileTool.ts b/src/core/tools/readFileTool.ts index 315f178e32..8cbe89e570 100644 --- a/src/core/tools/readFileTool.ts +++ b/src/core/tools/readFileTool.ts @@ -21,164 +21,159 @@ export async function readFileTool( pushToolResult: PushToolResult, removeClosingTag: RemoveClosingTag, ) { - switch (true) { - default: - const relPath: string | undefined = block.params.path - const startLineStr: string | undefined = block.params.start_line - const endLineStr: string | undefined = block.params.end_line + const relPath: string | undefined = block.params.path + const startLineStr: string | undefined = block.params.start_line + const endLineStr: string | undefined = block.params.end_line - // Get the full path and determine if it's outside the workspace - const fullPath = relPath ? path.resolve(cline.cwd, removeClosingTag("path", relPath)) : "" - const isOutsideWorkspace = isPathOutsideWorkspace(fullPath) + // Get the full path and determine if it's outside the workspace + const fullPath = relPath ? path.resolve(cline.cwd, removeClosingTag("path", relPath)) : "" + const isOutsideWorkspace = isPathOutsideWorkspace(fullPath) - const sharedMessageProps: ClineSayTool = { - tool: "readFile", - path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), - isOutsideWorkspace, + const sharedMessageProps: ClineSayTool = { + tool: "readFile", + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + isOutsideWorkspace, + } + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: undefined, + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!relPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("read_file", "path")) + return } - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: undefined, - } satisfies ClineSayTool) - await cline.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!relPath) { - cline.consecutiveMistakeCount++ - pushToolResult(await cline.sayAndCreateMissingParamError("read_file", "path")) - break - } - // Check if we're doing a line range read - let isRangeRead = false - let startLine: number | undefined = undefined - let endLine: number | undefined = undefined + // Check if we're doing a line range read + let isRangeRead = false + let startLine: number | undefined = undefined + let endLine: number | undefined = undefined - // Check if we have either range parameter - if (startLineStr || endLineStr) { - isRangeRead = true - } + // Check if we have either range parameter + if (startLineStr || endLineStr) { + isRangeRead = true + } - // Parse start_line if provided - if (startLineStr) { - startLine = parseInt(startLineStr) - if (isNaN(startLine)) { - // Invalid start_line - cline.consecutiveMistakeCount++ - await cline.say("error", `Failed to parse start_line: ${startLineStr}`) - pushToolResult(formatResponse.toolError("Invalid start_line value")) - break - } - startLine -= 1 // Convert to 0-based index - } - - // Parse end_line if provided - if (endLineStr) { - endLine = parseInt(endLineStr) - - if (isNaN(endLine)) { - // Invalid end_line - cline.consecutiveMistakeCount++ - await cline.say("error", `Failed to parse end_line: ${endLineStr}`) - pushToolResult(formatResponse.toolError("Invalid end_line value")) - break - } - - // Convert to 0-based index - endLine -= 1 - } - - const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) - if (!accessAllowed) { - await cline.say("rooignore_error", relPath) - pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) - - break - } - - const { maxReadFileLine = 500 } = (await cline.providerRef.deref()?.getState()) ?? {} - - // Create line snippet description for approval message - let lineSnippet = "" - if (startLine !== undefined && endLine !== undefined) { - lineSnippet = t("tools:readFile.linesRange", { start: startLine + 1, end: endLine + 1 }) - } else if (startLine !== undefined) { - lineSnippet = t("tools:readFile.linesFromToEnd", { start: startLine + 1 }) - } else if (endLine !== undefined) { - lineSnippet = t("tools:readFile.linesFromStartTo", { end: endLine + 1 }) - } else if (maxReadFileLine === 0) { - lineSnippet = t("tools:readFile.definitionsOnly") - } else if (maxReadFileLine > 0) { - lineSnippet = t("tools:readFile.maxLines", { max: maxReadFileLine }) - } - - cline.consecutiveMistakeCount = 0 - const absolutePath = path.resolve(cline.cwd, relPath) - - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: absolutePath, - reason: lineSnippet, - } satisfies ClineSayTool) - - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - - // Count total lines in the file - let totalLines = 0 - try { - totalLines = await countFileLines(absolutePath) - } catch (error) { - console.error(`Error counting lines in file ${absolutePath}:`, error) - } - - // now execute the tool like normal - let content: string - let isFileTruncated = false - let sourceCodeDef = "" - - const isBinary = await isBinaryFile(absolutePath).catch(() => false) - - if (isRangeRead) { - if (startLine === undefined) { - content = addLineNumbers(await readLines(absolutePath, endLine, startLine)) - } else { - content = addLineNumbers(await readLines(absolutePath, endLine, startLine), startLine + 1) - } - } else if (!isBinary && maxReadFileLine >= 0 && totalLines > maxReadFileLine) { - // If file is too large, only read the first maxReadFileLine lines - isFileTruncated = true - - const res = await Promise.all([ - maxReadFileLine > 0 ? readLines(absolutePath, maxReadFileLine - 1, 0) : "", - parseSourceCodeDefinitionsForFile(absolutePath, cline.rooIgnoreController), - ]) - - content = res[0].length > 0 ? addLineNumbers(res[0]) : "" - const result = res[1] - if (result) { - sourceCodeDef = `\n\n${result}` - } - } else { - // Read entire file - content = await extractTextFromFile(absolutePath) - } - - // Add truncation notice if applicable - if (isFileTruncated) { - content += `\n\n[Showing only ${maxReadFileLine} of ${totalLines} total lines. Use start_line and end_line if you need to read more]${sourceCodeDef}` - } - - pushToolResult(content) - break + // Parse start_line if provided + if (startLineStr) { + startLine = parseInt(startLineStr) + if (isNaN(startLine)) { + // Invalid start_line + cline.consecutiveMistakeCount++ + await cline.say("error", `Failed to parse start_line: ${startLineStr}`) + pushToolResult(formatResponse.toolError("Invalid start_line value")) + return } - } catch (error) { - await handleError("reading file", error) - break + startLine -= 1 // Convert to 0-based index } + + // Parse end_line if provided + if (endLineStr) { + endLine = parseInt(endLineStr) + + if (isNaN(endLine)) { + // Invalid end_line + cline.consecutiveMistakeCount++ + await cline.say("error", `Failed to parse end_line: ${endLineStr}`) + pushToolResult(formatResponse.toolError("Invalid end_line value")) + return + } + + // Convert to 0-based index + endLine -= 1 + } + + const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) + if (!accessAllowed) { + await cline.say("rooignore_error", relPath) + pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) + + return + } + + const { maxReadFileLine = 500 } = (await cline.providerRef.deref()?.getState()) ?? {} + + // Create line snippet description for approval message + let lineSnippet = "" + if (startLine !== undefined && endLine !== undefined) { + lineSnippet = t("tools:readFile.linesRange", { start: startLine + 1, end: endLine + 1 }) + } else if (startLine !== undefined) { + lineSnippet = t("tools:readFile.linesFromToEnd", { start: startLine + 1 }) + } else if (endLine !== undefined) { + lineSnippet = t("tools:readFile.linesFromStartTo", { end: endLine + 1 }) + } else if (maxReadFileLine === 0) { + lineSnippet = t("tools:readFile.definitionsOnly") + } else if (maxReadFileLine > 0) { + lineSnippet = t("tools:readFile.maxLines", { max: maxReadFileLine }) + } + + cline.consecutiveMistakeCount = 0 + const absolutePath = path.resolve(cline.cwd, relPath) + + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: absolutePath, + reason: lineSnippet, + } satisfies ClineSayTool) + + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return + } + + // Count total lines in the file + let totalLines = 0 + try { + totalLines = await countFileLines(absolutePath) + } catch (error) { + console.error(`Error counting lines in file ${absolutePath}:`, error) + } + + // now execute the tool like normal + let content: string + let isFileTruncated = false + let sourceCodeDef = "" + + const isBinary = await isBinaryFile(absolutePath).catch(() => false) + + if (isRangeRead) { + if (startLine === undefined) { + content = addLineNumbers(await readLines(absolutePath, endLine, startLine)) + } else { + content = addLineNumbers(await readLines(absolutePath, endLine, startLine), startLine + 1) + } + } else if (!isBinary && maxReadFileLine >= 0 && totalLines > maxReadFileLine) { + // If file is too large, only read the first maxReadFileLine lines + isFileTruncated = true + + const res = await Promise.all([ + maxReadFileLine > 0 ? readLines(absolutePath, maxReadFileLine - 1, 0) : "", + parseSourceCodeDefinitionsForFile(absolutePath, cline.rooIgnoreController), + ]) + + content = res[0].length > 0 ? addLineNumbers(res[0]) : "" + const result = res[1] + if (result) { + sourceCodeDef = `\n\n${result}` + } + } else { + // Read entire file + content = await extractTextFromFile(absolutePath) + } + + // Add truncation notice if applicable + if (isFileTruncated) { + content += `\n\n[Showing only ${maxReadFileLine} of ${totalLines} total lines. Use start_line and end_line if you need to read more]${sourceCodeDef}` + } + + pushToolResult(content) + } + } catch (error) { + await handleError("reading file", error) } } diff --git a/src/core/tools/types.ts b/src/core/tools/types.ts index 87d7d1ed36..0d21b9e3ee 100644 --- a/src/core/tools/types.ts +++ b/src/core/tools/types.ts @@ -8,7 +8,7 @@ export type AskApproval = ( progressStatus?: ToolProgressStatus, ) => Promise -export type HandleError = (action: string, error: Error) => void +export type HandleError = (action: string, error: Error) => Promise export type PushToolResult = (content: ToolResponse) => void From 1242ba808e766fee746ae1aa6cac19b768cdd2a9 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 00:37:13 -0400 Subject: [PATCH 15/38] Fix a type in the listFiles tool (#2092) --- src/core/tools/listFilesTool.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/tools/listFilesTool.ts b/src/core/tools/listFilesTool.ts index 838d9886f1..efc9226e0a 100644 --- a/src/core/tools/listFilesTool.ts +++ b/src/core/tools/listFilesTool.ts @@ -5,7 +5,7 @@ import { ToolParamName, ToolUse } from "../assistant-message" import { formatResponse } from "../prompts/responses" import { listFiles } from "../../services/glob/list-files" import { getReadablePath } from "../../utils/path" -import { AskApproval, HandleError, PushToolResult } from "./types" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" /** * Implements the list_files tool. * @@ -26,7 +26,7 @@ export async function listFilesTool( askApproval: AskApproval, handleError: HandleError, pushToolResult: PushToolResult, - removeClosingTag: (tag: ToolParamName, text?: string) => string, + removeClosingTag: RemoveClosingTag, ) { const relDirPath: string | undefined = block.params.path const recursiveRaw: string | undefined = block.params.recursive From 26c3fe6535b2c265b89a9ce435736a6db743a059 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 00:49:08 -0400 Subject: [PATCH 16/38] Move write_to_file to a tool file (#2093) --- src/core/Cline.ts | 213 +----------------------------- src/core/tools/writeToFileTool.ts | 209 +++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+), 207 deletions(-) create mode 100644 src/core/tools/writeToFileTool.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 142f6bd3db..6f6984c915 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -85,6 +85,7 @@ import { telemetryService } from "../services/telemetry/TelemetryService" import { validateToolUse, isToolAllowedForMode, ToolName } from "./mode-validator" import { parseXml } from "../utils/xml" import { getWorkspacePath } from "../utils/path" +import { writeToFileTool } from "./tools/writeToFileTool" export type ToolResponse = string | Array type UserContent = Array @@ -135,7 +136,7 @@ export class Cline extends EventEmitter { api: ApiHandler private urlContentFetcher: UrlContentFetcher private browserSession: BrowserSession - private didEditFile: boolean = false + didEditFile: boolean = false customInstructions?: string diffStrategy?: DiffStrategy diffEnabled: boolean = false @@ -156,7 +157,7 @@ export class Cline extends EventEmitter { private abort: boolean = false didFinishAbortingStream = false abandoned = false - private diffViewProvider: DiffViewProvider + diffViewProvider: DiffViewProvider private lastApiRequestTime?: number isInitialized = false @@ -1564,211 +1565,9 @@ export class Cline extends EventEmitter { } switch (block.name) { - case "write_to_file": { - const relPath: string | undefined = block.params.path - let newContent: string | undefined = block.params.content - let predictedLineCount: number | undefined = parseInt(block.params.line_count ?? "0") - if (!relPath || !newContent) { - // checking for newContent ensure relPath is complete - // wait so we can determine if it's a new file or editing an existing file - break - } - - const accessAllowed = this.rooIgnoreController?.validateAccess(relPath) - if (!accessAllowed) { - await this.say("rooignore_error", relPath) - pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) - - break - } - - // Check if file exists using cached map or fs.access - let fileExists: boolean - if (this.diffViewProvider.editType !== undefined) { - fileExists = this.diffViewProvider.editType === "modify" - } else { - const absolutePath = path.resolve(this.cwd, relPath) - fileExists = await fileExistsAtPath(absolutePath) - this.diffViewProvider.editType = fileExists ? "modify" : "create" - } - - // pre-processing newContent for cases where weaker models might add artifacts like markdown codeblock markers (deepseek/llama) or extra escape characters (gemini) - if (newContent.startsWith("```")) { - // this handles cases where it includes language specifiers like ```python ```js - newContent = newContent.split("\n").slice(1).join("\n").trim() - } - if (newContent.endsWith("```")) { - newContent = newContent.split("\n").slice(0, -1).join("\n").trim() - } - - if (!this.api.getModel().id.includes("claude")) { - // it seems not just llama models are doing this, but also gemini and potentially others - if ( - newContent.includes(">") || - newContent.includes("<") || - newContent.includes(""") - ) { - newContent = newContent - .replace(/>/g, ">") - .replace(/</g, "<") - .replace(/"/g, '"') - } - } - - // Determine if the path is outside the workspace - const fullPath = relPath ? path.resolve(this.cwd, removeClosingTag("path", relPath)) : "" - const isOutsideWorkspace = isPathOutsideWorkspace(fullPath) - - const sharedMessageProps: ClineSayTool = { - tool: fileExists ? "editedExistingFile" : "newFileCreated", - path: getReadablePath(this.cwd, removeClosingTag("path", relPath)), - isOutsideWorkspace, - } - try { - if (block.partial) { - // update gui message - const partialMessage = JSON.stringify(sharedMessageProps) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - // update editor - if (!this.diffViewProvider.isEditing) { - // open the editor and prepare to stream content in - await this.diffViewProvider.open(relPath) - } - // editor is open, stream content in - await this.diffViewProvider.update( - everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, - false, - ) - break - } else { - if (!relPath) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "path")) - await this.diffViewProvider.reset() - break - } - if (!newContent) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("write_to_file", "content")) - await this.diffViewProvider.reset() - break - } - if (!predictedLineCount) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("write_to_file", "line_count"), - ) - await this.diffViewProvider.reset() - break - } - this.consecutiveMistakeCount = 0 - - // if isEditingFile false, that means we have the full contents of the file already. - // it's important to note how this function works, you can't make the assumption that the block.partial conditional will always be called since it may immediately get complete, non-partial data. So this part of the logic will always be called. - // in other words, you must always repeat the block.partial logic here - if (!this.diffViewProvider.isEditing) { - // show gui message before showing edit animation - const partialMessage = JSON.stringify(sharedMessageProps) - await this.ask("tool", partialMessage, true).catch(() => {}) // sending true for partial even though it's not a partial, this shows the edit row before the content is streamed into the editor - await this.diffViewProvider.open(relPath) - } - await this.diffViewProvider.update( - everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, - true, - ) - await delay(300) // wait for diff view to update - this.diffViewProvider.scrollToFirstDiff() - - // Check for code omissions before proceeding - if ( - detectCodeOmission( - this.diffViewProvider.originalContent || "", - newContent, - predictedLineCount, - ) - ) { - if (this.diffStrategy) { - await this.diffViewProvider.revertChanges() - pushToolResult( - formatResponse.toolError( - `Content appears to be truncated (file has ${ - newContent.split("\n").length - } lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`, - ), - ) - break - } else { - vscode.window - .showWarningMessage( - "Potential code truncation detected. This happens when the AI reaches its max output limit.", - "Follow this guide to fix the issue", - ) - .then((selection) => { - if (selection === "Follow this guide to fix the issue") { - vscode.env.openExternal( - vscode.Uri.parse( - "https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments", - ), - ) - } - }) - } - } - - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: fileExists ? undefined : newContent, - diff: fileExists - ? formatResponse.createPrettyPatch( - relPath, - this.diffViewProvider.originalContent, - newContent, - ) - : undefined, - } satisfies ClineSayTool) - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - await this.diffViewProvider.revertChanges() - break - } - const { newProblemsMessage, userEdits, finalContent } = - await this.diffViewProvider.saveChanges() - this.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request - if (userEdits) { - await this.say( - "user_feedback_diff", - JSON.stringify({ - tool: fileExists ? "editedExistingFile" : "newFileCreated", - path: getReadablePath(this.cwd, relPath), - diff: userEdits, - } satisfies ClineSayTool), - ) - pushToolResult( - `The user made the following updates to your content:\n\n${userEdits}\n\n` + - `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + - `\n${addLineNumbers( - finalContent || "", - )}\n\n\n` + - `Please note:\n` + - `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + - `2. Proceed with the task using this updated file content as the new baseline.\n` + - `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + - `${newProblemsMessage}`, - ) - } else { - pushToolResult( - `The content was successfully saved to ${relPath.toPosix()}.${newProblemsMessage}`, - ) - } - await this.diffViewProvider.reset() - break - } - } catch (error) { - await handleError("writing file", error) - await this.diffViewProvider.reset() - break - } - } + case "write_to_file": + await writeToFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break case "apply_diff": { const relPath: string | undefined = block.params.path const diffContent: string | undefined = block.params.diff diff --git a/src/core/tools/writeToFileTool.ts b/src/core/tools/writeToFileTool.ts new file mode 100644 index 0000000000..5c24584b91 --- /dev/null +++ b/src/core/tools/writeToFileTool.ts @@ -0,0 +1,209 @@ +import * as vscode from "vscode" + +import { Cline } from "../Cline" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { ToolUse } from "../assistant-message" +import { formatResponse } from "../prompts/responses" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import path from "path" +import { fileExistsAtPath } from "../../utils/fs" +import { addLineNumbers, stripLineNumbers } from "../../integrations/misc/extract-text" +import { getReadablePath } from "../../utils/path" +import { isPathOutsideWorkspace } from "../../utils/pathUtils" +import { everyLineHasLineNumbers } from "../../integrations/misc/extract-text" +import delay from "delay" +import { detectCodeOmission } from "../../integrations/editor/detect-omission" + +export async function writeToFileTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relPath: string | undefined = block.params.path + let newContent: string | undefined = block.params.content + let predictedLineCount: number | undefined = parseInt(block.params.line_count ?? "0") + if (!relPath || !newContent) { + // checking for newContent ensure relPath is complete + // wait so we can determine if it's a new file or editing an existing file + return + } + + const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) + if (!accessAllowed) { + await cline.say("rooignore_error", relPath) + pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) + + return + } + + // Check if file exists using cached map or fs.access + let fileExists: boolean + if (cline.diffViewProvider.editType !== undefined) { + fileExists = cline.diffViewProvider.editType === "modify" + } else { + const absolutePath = path.resolve(cline.cwd, relPath) + fileExists = await fileExistsAtPath(absolutePath) + cline.diffViewProvider.editType = fileExists ? "modify" : "create" + } + + // pre-processing newContent for cases where weaker models might add artifacts like markdown codeblock markers (deepseek/llama) or extra escape characters (gemini) + if (newContent.startsWith("```")) { + // cline handles cases where it includes language specifiers like ```python ```js + newContent = newContent.split("\n").slice(1).join("\n").trim() + } + if (newContent.endsWith("```")) { + newContent = newContent.split("\n").slice(0, -1).join("\n").trim() + } + + if (!cline.api.getModel().id.includes("claude")) { + // it seems not just llama models are doing cline, but also gemini and potentially others + if (newContent.includes(">") || newContent.includes("<") || newContent.includes(""")) { + newContent = newContent + .replace(/>/g, ">") + .replace(/</g, "<") + .replace(/"/g, '"') + } + } + + // Determine if the path is outside the workspace + const fullPath = relPath ? path.resolve(cline.cwd, removeClosingTag("path", relPath)) : "" + const isOutsideWorkspace = isPathOutsideWorkspace(fullPath) + + const sharedMessageProps: ClineSayTool = { + tool: fileExists ? "editedExistingFile" : "newFileCreated", + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + isOutsideWorkspace, + } + try { + if (block.partial) { + // update gui message + const partialMessage = JSON.stringify(sharedMessageProps) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + // update editor + if (!cline.diffViewProvider.isEditing) { + // open the editor and prepare to stream content in + await cline.diffViewProvider.open(relPath) + } + // editor is open, stream content in + await cline.diffViewProvider.update( + everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, + false, + ) + return + } else { + if (!relPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("write_to_file", "path")) + await cline.diffViewProvider.reset() + return + } + if (!newContent) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("write_to_file", "content")) + await cline.diffViewProvider.reset() + return + } + if (!predictedLineCount) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("write_to_file", "line_count")) + await cline.diffViewProvider.reset() + return + } + cline.consecutiveMistakeCount = 0 + + // if isEditingFile false, that means we have the full contents of the file already. + // it's important to note how cline function works, you can't make the assumption that the block.partial conditional will always be called since it may immediately get complete, non-partial data. So cline part of the logic will always be called. + // in other words, you must always repeat the block.partial logic here + if (!cline.diffViewProvider.isEditing) { + // show gui message before showing edit animation + const partialMessage = JSON.stringify(sharedMessageProps) + await cline.ask("tool", partialMessage, true).catch(() => {}) // sending true for partial even though it's not a partial, cline shows the edit row before the content is streamed into the editor + await cline.diffViewProvider.open(relPath) + } + await cline.diffViewProvider.update( + everyLineHasLineNumbers(newContent) ? stripLineNumbers(newContent) : newContent, + true, + ) + await delay(300) // wait for diff view to update + cline.diffViewProvider.scrollToFirstDiff() + + // Check for code omissions before proceeding + if (detectCodeOmission(cline.diffViewProvider.originalContent || "", newContent, predictedLineCount)) { + if (cline.diffStrategy) { + await cline.diffViewProvider.revertChanges() + pushToolResult( + formatResponse.toolError( + `Content appears to be truncated (file has ${ + newContent.split("\n").length + } lines but was predicted to have ${predictedLineCount} lines), and found comments indicating omitted code (e.g., '// rest of code unchanged', '/* previous code */'). Please provide the complete file content without any omissions if possible, or otherwise use the 'apply_diff' tool to apply the diff to the original file.`, + ), + ) + return + } else { + vscode.window + .showWarningMessage( + "Potential code truncation detected. cline happens when the AI reaches its max output limit.", + "Follow cline guide to fix the issue", + ) + .then((selection) => { + if (selection === "Follow cline guide to fix the issue") { + vscode.env.openExternal( + vscode.Uri.parse( + "https://github.com/cline/cline/wiki/Troubleshooting-%E2%80%90-Cline-Deleting-Code-with-%22Rest-of-Code-Here%22-Comments", + ), + ) + } + }) + } + } + + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: fileExists ? undefined : newContent, + diff: fileExists + ? formatResponse.createPrettyPatch(relPath, cline.diffViewProvider.originalContent, newContent) + : undefined, + } satisfies ClineSayTool) + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + await cline.diffViewProvider.revertChanges() + return + } + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() + cline.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request + if (userEdits) { + await cline.say( + "user_feedback_diff", + JSON.stringify({ + tool: fileExists ? "editedExistingFile" : "newFileCreated", + path: getReadablePath(cline.cwd, relPath), + diff: userEdits, + } satisfies ClineSayTool), + ) + pushToolResult( + `The user made the following updates to your content:\n\n${userEdits}\n\n` + + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + + `\n${addLineNumbers( + finalContent || "", + )}\n\n\n` + + `Please note:\n` + + `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + + `2. Proceed with the task using cline updated file content as the new baseline.\n` + + `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + + `${newProblemsMessage}`, + ) + } else { + pushToolResult(`The content was successfully saved to ${relPath.toPosix()}.${newProblemsMessage}`) + } + await cline.diffViewProvider.reset() + return + } + } catch (error) { + await handleError("writing file", error) + await cline.diffViewProvider.reset() + return + } +} From 02f63fc522dc6e1edb13ab1923c76d87226f976b Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 01:04:38 -0400 Subject: [PATCH 17/38] Move apply_diff to a tool file (#2094) --- src/core/Cline.ts | 178 +------------------------------ src/core/tools/applyDiffTool.ts | 181 ++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+), 173 deletions(-) create mode 100644 src/core/tools/applyDiffTool.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 6f6984c915..e8e2e72deb 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -86,6 +86,7 @@ import { validateToolUse, isToolAllowedForMode, ToolName } from "./mode-validato import { parseXml } from "../utils/xml" import { getWorkspacePath } from "../utils/path" import { writeToFileTool } from "./tools/writeToFileTool" +import { applyDiffTool } from "./tools/applyDiffTool" export type ToolResponse = string | Array type UserContent = Array @@ -151,7 +152,7 @@ export class Cline extends EventEmitter { private lastMessageTs?: number // Not private since it needs to be accessible by tools consecutiveMistakeCount: number = 0 - private consecutiveMistakeCountForApplyDiff: Map = new Map() + consecutiveMistakeCountForApplyDiff: Map = new Map() // Not private since it needs to be accessible by tools providerRef: WeakRef private abort: boolean = false @@ -1568,178 +1569,9 @@ export class Cline extends EventEmitter { case "write_to_file": await writeToFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - case "apply_diff": { - const relPath: string | undefined = block.params.path - const diffContent: string | undefined = block.params.diff - - const sharedMessageProps: ClineSayTool = { - tool: "appliedDiff", - path: getReadablePath(this.cwd, removeClosingTag("path", relPath)), - } - - 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( - () => {}, - ) - break - } else { - if (!relPath) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("apply_diff", "path")) - break - } - if (!diffContent) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("apply_diff", "diff")) - break - } - - const accessAllowed = this.rooIgnoreController?.validateAccess(relPath) - if (!accessAllowed) { - await this.say("rooignore_error", relPath) - pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) - - break - } - - const absolutePath = path.resolve(this.cwd, relPath) - const fileExists = await fileExistsAtPath(absolutePath) - - if (!fileExists) { - this.consecutiveMistakeCount++ - const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` - await this.say("error", formattedError) - pushToolResult(formattedError) - break - } - - const originalContent = await fs.readFile(absolutePath, "utf-8") - - // Apply the diff to the original content - const diffResult = (await this.diffStrategy?.applyDiff( - originalContent, - diffContent, - parseInt(block.params.start_line ?? ""), - parseInt(block.params.end_line ?? ""), - )) ?? { - success: false, - error: "No diff strategy available", - } - let partResults = "" - - if (!diffResult.success) { - this.consecutiveMistakeCount++ - const currentCount = - (this.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1 - this.consecutiveMistakeCountForApplyDiff.set(relPath, currentCount) - let formattedError = "" - if (diffResult.failParts && diffResult.failParts.length > 0) { - for (const failPart of diffResult.failParts) { - if (failPart.success) { - continue - } - const errorDetails = failPart.details - ? JSON.stringify(failPart.details, null, 2) - : "" - formattedError = `\n${ - failPart.error - }${errorDetails ? `\n\nDetails:\n${errorDetails}` : ""}\n` - partResults += formattedError - } - } else { - const errorDetails = diffResult.details - ? JSON.stringify(diffResult.details, null, 2) - : "" - formattedError = `Unable to apply diff to file: ${absolutePath}\n\n\n${ - diffResult.error - }${errorDetails ? `\n\nDetails:\n${errorDetails}` : ""}\n` - } - - if (currentCount >= 2) { - await this.say("error", formattedError) - } - pushToolResult(formattedError) - break - } - - this.consecutiveMistakeCount = 0 - this.consecutiveMistakeCountForApplyDiff.delete(relPath) - // Show diff view before asking for approval - this.diffViewProvider.editType = "modify" - await this.diffViewProvider.open(relPath) - await this.diffViewProvider.update(diffResult.content, true) - await this.diffViewProvider.scrollToFirstDiff() - - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - diff: diffContent, - } satisfies ClineSayTool) - - let toolProgressStatus - if (this.diffStrategy && this.diffStrategy.getProgressStatus) { - toolProgressStatus = this.diffStrategy.getProgressStatus(block, diffResult) - } - - const didApprove = await askApproval("tool", completeMessage, toolProgressStatus) - if (!didApprove) { - await this.diffViewProvider.revertChanges() // This likely handles closing the diff view - break - } - - const { newProblemsMessage, userEdits, finalContent } = - await this.diffViewProvider.saveChanges() - this.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request - let partFailHint = "" - if (diffResult.failParts && diffResult.failParts.length > 0) { - partFailHint = `But unable to apply all diff parts to file: ${absolutePath}, silently use tool to check newest file version and re-apply diffs\n` - } - if (userEdits) { - await this.say( - "user_feedback_diff", - JSON.stringify({ - tool: fileExists ? "editedExistingFile" : "newFileCreated", - path: getReadablePath(this.cwd, relPath), - diff: userEdits, - } satisfies ClineSayTool), - ) - pushToolResult( - `The user made the following updates to your content:\n\n${userEdits}\n\n` + - partFailHint + - `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + - `\n${addLineNumbers( - finalContent || "", - )}\n\n\n` + - `Please note:\n` + - `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + - `2. Proceed with the task using this updated file content as the new baseline.\n` + - `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + - `${newProblemsMessage}`, - ) - } else { - pushToolResult( - `Changes successfully applied to ${relPath.toPosix()}:\n\n${newProblemsMessage}\n` + - partFailHint, - ) - } - await this.diffViewProvider.reset() - break - } - } catch (error) { - await handleError("applying diff", error) - await this.diffViewProvider.reset() - break - } - } - + case "apply_diff": + await applyDiffTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break case "insert_content": { const relPath: string | undefined = block.params.path const operations: string | undefined = block.params.operations diff --git a/src/core/tools/applyDiffTool.ts b/src/core/tools/applyDiffTool.ts new file mode 100644 index 0000000000..a20bace097 --- /dev/null +++ b/src/core/tools/applyDiffTool.ts @@ -0,0 +1,181 @@ +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { getReadablePath } from "../../utils/path" +import { ToolUse } from "../assistant-message" +import { Cline } from "../Cline" +import { RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" +import { AskApproval, HandleError, PushToolResult } from "./types" +import { fileExistsAtPath } from "../../utils/fs" +import { addLineNumbers } from "../../integrations/misc/extract-text" +import path from "path" +import fs from "fs/promises" + +export async function applyDiffTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relPath: string | undefined = block.params.path + const diffContent: string | undefined = block.params.diff + + const sharedMessageProps: ClineSayTool = { + tool: "appliedDiff", + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + } + + try { + if (block.partial) { + // update gui message + let toolProgressStatus + if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) { + toolProgressStatus = cline.diffStrategy.getProgressStatus(block) + } + + const partialMessage = JSON.stringify(sharedMessageProps) + + await cline.ask("tool", partialMessage, block.partial, toolProgressStatus).catch(() => {}) + return + } else { + if (!relPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("apply_diff", "path")) + return + } + if (!diffContent) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("apply_diff", "diff")) + return + } + + const accessAllowed = cline.rooIgnoreController?.validateAccess(relPath) + if (!accessAllowed) { + await cline.say("rooignore_error", relPath) + pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(relPath))) + + return + } + + const absolutePath = path.resolve(cline.cwd, relPath) + const fileExists = await fileExistsAtPath(absolutePath) + + if (!fileExists) { + cline.consecutiveMistakeCount++ + const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` + await cline.say("error", formattedError) + pushToolResult(formattedError) + return + } + + const originalContent = await fs.readFile(absolutePath, "utf-8") + + // Apply the diff to the original content + const diffResult = (await cline.diffStrategy?.applyDiff( + originalContent, + diffContent, + parseInt(block.params.start_line ?? ""), + parseInt(block.params.end_line ?? ""), + )) ?? { + success: false, + error: "No diff strategy available", + } + let partResults = "" + + if (!diffResult.success) { + cline.consecutiveMistakeCount++ + const currentCount = (cline.consecutiveMistakeCountForApplyDiff.get(relPath) || 0) + 1 + cline.consecutiveMistakeCountForApplyDiff.set(relPath, currentCount) + let formattedError = "" + if (diffResult.failParts && diffResult.failParts.length > 0) { + for (const failPart of diffResult.failParts) { + if (failPart.success) { + continue + } + const errorDetails = failPart.details ? JSON.stringify(failPart.details, null, 2) : "" + formattedError = `\n${ + failPart.error + }${errorDetails ? `\n\nDetails:\n${errorDetails}` : ""}\n` + partResults += formattedError + } + } else { + const errorDetails = diffResult.details ? JSON.stringify(diffResult.details, null, 2) : "" + formattedError = `Unable to apply diff to file: ${absolutePath}\n\n\n${ + diffResult.error + }${errorDetails ? `\n\nDetails:\n${errorDetails}` : ""}\n` + } + + if (currentCount >= 2) { + await cline.say("error", formattedError) + } + pushToolResult(formattedError) + return + } + + cline.consecutiveMistakeCount = 0 + cline.consecutiveMistakeCountForApplyDiff.delete(relPath) + // Show diff view before asking for approval + cline.diffViewProvider.editType = "modify" + await cline.diffViewProvider.open(relPath) + await cline.diffViewProvider.update(diffResult.content, true) + await cline.diffViewProvider.scrollToFirstDiff() + + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + diff: diffContent, + } satisfies ClineSayTool) + + let toolProgressStatus + if (cline.diffStrategy && cline.diffStrategy.getProgressStatus) { + toolProgressStatus = cline.diffStrategy.getProgressStatus(block, diffResult) + } + + const didApprove = await askApproval("tool", completeMessage, toolProgressStatus) + if (!didApprove) { + await cline.diffViewProvider.revertChanges() // cline likely handles closing the diff view + return + } + + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() + cline.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request + let partFailHint = "" + if (diffResult.failParts && diffResult.failParts.length > 0) { + partFailHint = `But unable to apply all diff parts to file: ${absolutePath}, silently use tool to check newest file version and re-apply diffs\n` + } + if (userEdits) { + await cline.say( + "user_feedback_diff", + JSON.stringify({ + tool: fileExists ? "editedExistingFile" : "newFileCreated", + path: getReadablePath(cline.cwd, relPath), + diff: userEdits, + } satisfies ClineSayTool), + ) + pushToolResult( + `The user made the following updates to your content:\n\n${userEdits}\n\n` + + partFailHint + + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + + `\n${addLineNumbers( + finalContent || "", + )}\n\n\n` + + `Please note:\n` + + `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + + `2. Proceed with the task using cline updated file content as the new baseline.\n` + + `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + + `${newProblemsMessage}`, + ) + } else { + pushToolResult( + `Changes successfully applied to ${relPath.toPosix()}:\n\n${newProblemsMessage}\n` + partFailHint, + ) + } + await cline.diffViewProvider.reset() + return + } + } catch (error) { + await handleError("applying diff", error) + await cline.diffViewProvider.reset() + return + } +} From d5328770e789068bc0cc75dca3884a7fa16a9527 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 01:10:26 -0400 Subject: [PATCH 18/38] Move insert_content to a tool file (#2095) --- src/core/Cline.ts | 149 +------------------------ src/core/tools/insertContentTool.ts | 161 ++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+), 146 deletions(-) create mode 100644 src/core/tools/insertContentTool.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index e8e2e72deb..937417a8d9 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -87,6 +87,7 @@ import { parseXml } from "../utils/xml" import { getWorkspacePath } from "../utils/path" import { writeToFileTool } from "./tools/writeToFileTool" import { applyDiffTool } from "./tools/applyDiffTool" +import { insertContentTool } from "./tools/insertContentTool" export type ToolResponse = string | Array type UserContent = Array @@ -1572,153 +1573,9 @@ export class Cline extends EventEmitter { case "apply_diff": await applyDiffTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - case "insert_content": { - const relPath: string | undefined = block.params.path - const operations: string | undefined = block.params.operations - - const sharedMessageProps: ClineSayTool = { - tool: "appliedDiff", - path: getReadablePath(this.cwd, removeClosingTag("path", relPath)), - } - - try { - if (block.partial) { - const partialMessage = JSON.stringify(sharedMessageProps) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } - - // Validate required parameters - if (!relPath) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("insert_content", "path")) - break - } - - if (!operations) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("insert_content", "operations")) - break - } - - const absolutePath = path.resolve(this.cwd, relPath) - const fileExists = await fileExistsAtPath(absolutePath) - - if (!fileExists) { - this.consecutiveMistakeCount++ - const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` - await this.say("error", formattedError) - pushToolResult(formattedError) - break - } - - let parsedOperations: Array<{ - start_line: number - content: string - }> - - try { - parsedOperations = JSON.parse(operations) - if (!Array.isArray(parsedOperations)) { - throw new Error("Operations must be an array") - } - } catch (error) { - this.consecutiveMistakeCount++ - await this.say("error", `Failed to parse operations JSON: ${error.message}`) - pushToolResult(formatResponse.toolError("Invalid operations JSON format")) - break - } - - this.consecutiveMistakeCount = 0 - - // Read the file - const fileContent = await fs.readFile(absolutePath, "utf8") - this.diffViewProvider.editType = "modify" - this.diffViewProvider.originalContent = fileContent - const lines = fileContent.split("\n") - - const updatedContent = insertGroups( - lines, - parsedOperations.map((elem) => { - return { - index: elem.start_line - 1, - elements: elem.content.split("\n"), - } - }), - ).join("\n") - - // Show changes in diff view - if (!this.diffViewProvider.isEditing) { - await this.ask("tool", JSON.stringify(sharedMessageProps), true).catch(() => {}) - // First open with original content - await this.diffViewProvider.open(relPath) - await this.diffViewProvider.update(fileContent, false) - this.diffViewProvider.scrollToFirstDiff() - await delay(200) - } - - const diff = formatResponse.createPrettyPatch(relPath, fileContent, updatedContent) - - if (!diff) { - pushToolResult(`No changes needed for '${relPath}'`) - break - } - - await this.diffViewProvider.update(updatedContent, true) - - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - diff, - } satisfies ClineSayTool) - - const didApprove = await this.ask("tool", completeMessage, false).then( - (response) => response.response === "yesButtonClicked", - ) - - if (!didApprove) { - await this.diffViewProvider.revertChanges() - pushToolResult("Changes were rejected by the user.") - break - } - - const { newProblemsMessage, userEdits, finalContent } = - await this.diffViewProvider.saveChanges() - this.didEditFile = true - - if (!userEdits) { - pushToolResult( - `The content was successfully inserted in ${relPath.toPosix()}.${newProblemsMessage}`, - ) - await this.diffViewProvider.reset() - break - } - - const userFeedbackDiff = JSON.stringify({ - tool: "appliedDiff", - path: getReadablePath(this.cwd, relPath), - diff: userEdits, - } satisfies ClineSayTool) - - console.debug("[DEBUG] User made edits, sending feedback diff:", userFeedbackDiff) - await this.say("user_feedback_diff", userFeedbackDiff) - pushToolResult( - `The user made the following updates to your content:\n\n${userEdits}\n\n` + - `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file:\n\n` + - `\n${finalContent}\n\n\n` + - `Please note:\n` + - `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + - `2. Proceed with the task using this updated file content as the new baseline.\n` + - `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + - `${newProblemsMessage}`, - ) - await this.diffViewProvider.reset() - } catch (error) { - handleError("insert content", error) - await this.diffViewProvider.reset() - } + case "insert_content": + await insertContentTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - } - case "search_and_replace": { const relPath: string | undefined = block.params.path const operations: string | undefined = block.params.operations diff --git a/src/core/tools/insertContentTool.ts b/src/core/tools/insertContentTool.ts new file mode 100644 index 0000000000..9ff2b28429 --- /dev/null +++ b/src/core/tools/insertContentTool.ts @@ -0,0 +1,161 @@ +import { getReadablePath } from "../../utils/path" +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import path from "path" +import { fileExistsAtPath } from "../../utils/fs" +import { insertGroups } from "../diff/insert-groups" +import delay from "delay" +import fs from "fs/promises" + +export async function insertContentTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relPath: string | undefined = block.params.path + const operations: string | undefined = block.params.operations + + const sharedMessageProps: ClineSayTool = { + tool: "appliedDiff", + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + } + + try { + if (block.partial) { + const partialMessage = JSON.stringify(sharedMessageProps) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } + + // Validate required parameters + if (!relPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("insert_content", "path")) + return + } + + if (!operations) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("insert_content", "operations")) + return + } + + const absolutePath = path.resolve(cline.cwd, relPath) + const fileExists = await fileExistsAtPath(absolutePath) + + if (!fileExists) { + cline.consecutiveMistakeCount++ + const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` + await cline.say("error", formattedError) + pushToolResult(formattedError) + return + } + + let parsedOperations: Array<{ + start_line: number + content: string + }> + + try { + parsedOperations = JSON.parse(operations) + if (!Array.isArray(parsedOperations)) { + throw new Error("Operations must be an array") + } + } catch (error) { + cline.consecutiveMistakeCount++ + await cline.say("error", `Failed to parse operations JSON: ${error.message}`) + pushToolResult(formatResponse.toolError("Invalid operations JSON format")) + return + } + + cline.consecutiveMistakeCount = 0 + + // Read the file + const fileContent = await fs.readFile(absolutePath, "utf8") + cline.diffViewProvider.editType = "modify" + cline.diffViewProvider.originalContent = fileContent + const lines = fileContent.split("\n") + + const updatedContent = insertGroups( + lines, + parsedOperations.map((elem) => { + return { + index: elem.start_line - 1, + elements: elem.content.split("\n"), + } + }), + ).join("\n") + + // Show changes in diff view + if (!cline.diffViewProvider.isEditing) { + await cline.ask("tool", JSON.stringify(sharedMessageProps), true).catch(() => {}) + // First open with original content + await cline.diffViewProvider.open(relPath) + await cline.diffViewProvider.update(fileContent, false) + cline.diffViewProvider.scrollToFirstDiff() + await delay(200) + } + + const diff = formatResponse.createPrettyPatch(relPath, fileContent, updatedContent) + + if (!diff) { + pushToolResult(`No changes needed for '${relPath}'`) + return + } + + await cline.diffViewProvider.update(updatedContent, true) + + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + diff, + } satisfies ClineSayTool) + + const didApprove = await cline + .ask("tool", completeMessage, false) + .then((response) => response.response === "yesButtonClicked") + + if (!didApprove) { + await cline.diffViewProvider.revertChanges() + pushToolResult("Changes were rejected by the user.") + return + } + + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() + cline.didEditFile = true + + if (!userEdits) { + pushToolResult(`The content was successfully inserted in ${relPath.toPosix()}.${newProblemsMessage}`) + await cline.diffViewProvider.reset() + return + } + + const userFeedbackDiff = JSON.stringify({ + tool: "appliedDiff", + path: getReadablePath(cline.cwd, relPath), + diff: userEdits, + } satisfies ClineSayTool) + + console.debug("[DEBUG] User made edits, sending feedback diff:", userFeedbackDiff) + await cline.say("user_feedback_diff", userFeedbackDiff) + pushToolResult( + `The user made the following updates to your content:\n\n${userEdits}\n\n` + + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file:\n\n` + + `\n${finalContent}\n\n\n` + + `Please note:\n` + + `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + + `2. Proceed with the task using cline updated file content as the new baseline.\n` + + `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + + `${newProblemsMessage}`, + ) + await cline.diffViewProvider.reset() + } catch (error) { + handleError("insert content", error) + await cline.diffViewProvider.reset() + } +} From bdb668b68bf7ade54c1a658250973a839af4cacf Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 01:15:43 -0400 Subject: [PATCH 19/38] Move search_and_replace to a tool file (#2096) --- src/core/Cline.ts | 193 ++----------------------- src/core/tools/searchAndReplaceTool.ts | 181 +++++++++++++++++++++++ 2 files changed, 195 insertions(+), 179 deletions(-) create mode 100644 src/core/tools/searchAndReplaceTool.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 937417a8d9..e5ad82895e 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -88,6 +88,7 @@ import { getWorkspacePath } from "../utils/path" import { writeToFileTool } from "./tools/writeToFileTool" import { applyDiffTool } from "./tools/applyDiffTool" import { insertContentTool } from "./tools/insertContentTool" +import { searchAndReplaceTool } from "./tools/searchAndReplaceTool" export type ToolResponse = string | Array type UserContent = Array @@ -1576,187 +1577,25 @@ export class Cline extends EventEmitter { case "insert_content": await insertContentTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - case "search_and_replace": { - const relPath: string | undefined = block.params.path - const operations: string | undefined = block.params.operations - - const sharedMessageProps: ClineSayTool = { - tool: "appliedDiff", - path: getReadablePath(this.cwd, removeClosingTag("path", relPath)), - } - - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - path: removeClosingTag("path", relPath), - operations: removeClosingTag("operations", operations), - }) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!relPath) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("search_and_replace", "path"), - ) - break - } - if (!operations) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("search_and_replace", "operations"), - ) - break - } - - const absolutePath = path.resolve(this.cwd, relPath) - const fileExists = await fileExistsAtPath(absolutePath) - - if (!fileExists) { - this.consecutiveMistakeCount++ - const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` - await this.say("error", formattedError) - pushToolResult(formattedError) - break - } - - let parsedOperations: Array<{ - search: string - replace: string - start_line?: number - end_line?: number - use_regex?: boolean - ignore_case?: boolean - regex_flags?: string - }> - - try { - parsedOperations = JSON.parse(operations) - if (!Array.isArray(parsedOperations)) { - throw new Error("Operations must be an array") - } - } catch (error) { - this.consecutiveMistakeCount++ - await this.say("error", `Failed to parse operations JSON: ${error.message}`) - pushToolResult(formatResponse.toolError("Invalid operations JSON format")) - break - } - - // Read the original file content - const fileContent = await fs.readFile(absolutePath, "utf-8") - this.diffViewProvider.editType = "modify" - this.diffViewProvider.originalContent = fileContent - let lines = fileContent.split("\n") - - for (const op of parsedOperations) { - const flags = op.regex_flags ?? (op.ignore_case ? "gi" : "g") - const multilineFlags = flags.includes("m") ? flags : flags + "m" - - const searchPattern = op.use_regex - ? new RegExp(op.search, multilineFlags) - : new RegExp(escapeRegExp(op.search), multilineFlags) - - if (op.start_line || op.end_line) { - const startLine = Math.max((op.start_line ?? 1) - 1, 0) - const endLine = Math.min((op.end_line ?? lines.length) - 1, lines.length - 1) - - // Get the content before and after the target section - const beforeLines = lines.slice(0, startLine) - const afterLines = lines.slice(endLine + 1) - - // Get the target section and perform replacement - const targetContent = lines.slice(startLine, endLine + 1).join("\n") - const modifiedContent = targetContent.replace(searchPattern, op.replace) - const modifiedLines = modifiedContent.split("\n") - - // Reconstruct the full content with the modified section - lines = [...beforeLines, ...modifiedLines, ...afterLines] - } else { - // Global replacement - const fullContent = lines.join("\n") - const modifiedContent = fullContent.replace(searchPattern, op.replace) - lines = modifiedContent.split("\n") - } - } - - const newContent = lines.join("\n") - - this.consecutiveMistakeCount = 0 - - // Show diff preview - const diff = formatResponse.createPrettyPatch(relPath, fileContent, newContent) - - if (!diff) { - pushToolResult(`No changes needed for '${relPath}'`) - break - } - - await this.diffViewProvider.open(relPath) - await this.diffViewProvider.update(newContent, true) - this.diffViewProvider.scrollToFirstDiff() - - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - diff: diff, - } satisfies ClineSayTool) - - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - await this.diffViewProvider.revertChanges() // This likely handles closing the diff view - break - } - - const { newProblemsMessage, userEdits, finalContent } = - await this.diffViewProvider.saveChanges() - this.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request - if (userEdits) { - await this.say( - "user_feedback_diff", - JSON.stringify({ - tool: fileExists ? "editedExistingFile" : "newFileCreated", - path: getReadablePath(this.cwd, relPath), - diff: userEdits, - } satisfies ClineSayTool), - ) - pushToolResult( - `The user made the following updates to your content:\n\n${userEdits}\n\n` + - `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + - `\n${addLineNumbers(finalContent || "")}\n\n\n` + - `Please note:\n` + - `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + - `2. Proceed with the task using this updated file content as the new baseline.\n` + - `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + - `${newProblemsMessage}`, - ) - } else { - pushToolResult( - `Changes successfully applied to ${relPath.toPosix()}:\n\n${newProblemsMessage}`, - ) - } - await this.diffViewProvider.reset() - break - } - } catch (error) { - await handleError("applying search and replace", error) - await this.diffViewProvider.reset() - break - } - } - - case "read_file": { + case "search_and_replace": + await searchAndReplaceTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break + case "read_file": await readFileTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - } - - case "fetch_instructions": { + case "fetch_instructions": await fetchInstructionsTool(this, block, askApproval, handleError, pushToolResult) break - } - - case "list_files": { + case "list_files": await listFilesTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - } case "list_code_definition_names": { const relPath: string | undefined = block.params.path const sharedMessageProps: ClineSayTool = { @@ -3562,7 +3401,3 @@ export class Cline extends EventEmitter { } } } - -function escapeRegExp(string: string): string { - return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") -} diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts new file mode 100644 index 0000000000..3b204273bb --- /dev/null +++ b/src/core/tools/searchAndReplaceTool.ts @@ -0,0 +1,181 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { getReadablePath } from "../../utils/path" +import path from "path" +import { fileExistsAtPath } from "../../utils/fs" +import { addLineNumbers } from "../../integrations/misc/extract-text" +import fs from "fs/promises" + +export async function searchAndReplaceTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relPath: string | undefined = block.params.path + const operations: string | undefined = block.params.operations + + const sharedMessageProps: ClineSayTool = { + tool: "appliedDiff", + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + } + + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + path: removeClosingTag("path", relPath), + operations: removeClosingTag("operations", operations), + }) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!relPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("search_and_replace", "path")) + return + } + if (!operations) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("search_and_replace", "operations")) + return + } + + const absolutePath = path.resolve(cline.cwd, relPath) + const fileExists = await fileExistsAtPath(absolutePath) + + if (!fileExists) { + cline.consecutiveMistakeCount++ + const formattedError = `File does not exist at path: ${absolutePath}\n\n\nThe specified file could not be found. Please verify the file path and try again.\n` + await cline.say("error", formattedError) + pushToolResult(formattedError) + return + } + + let parsedOperations: Array<{ + search: string + replace: string + start_line?: number + end_line?: number + use_regex?: boolean + ignore_case?: boolean + regex_flags?: string + }> + + try { + parsedOperations = JSON.parse(operations) + if (!Array.isArray(parsedOperations)) { + throw new Error("Operations must be an array") + } + } catch (error) { + cline.consecutiveMistakeCount++ + await cline.say("error", `Failed to parse operations JSON: ${error.message}`) + pushToolResult(formatResponse.toolError("Invalid operations JSON format")) + return + } + + // Read the original file content + const fileContent = await fs.readFile(absolutePath, "utf-8") + cline.diffViewProvider.editType = "modify" + cline.diffViewProvider.originalContent = fileContent + let lines = fileContent.split("\n") + + for (const op of parsedOperations) { + const flags = op.regex_flags ?? (op.ignore_case ? "gi" : "g") + const multilineFlags = flags.includes("m") ? flags : flags + "m" + + const searchPattern = op.use_regex + ? new RegExp(op.search, multilineFlags) + : new RegExp(escapeRegExp(op.search), multilineFlags) + + if (op.start_line || op.end_line) { + const startLine = Math.max((op.start_line ?? 1) - 1, 0) + const endLine = Math.min((op.end_line ?? lines.length) - 1, lines.length - 1) + + // Get the content before and after the target section + const beforeLines = lines.slice(0, startLine) + const afterLines = lines.slice(endLine + 1) + + // Get the target section and perform replacement + const targetContent = lines.slice(startLine, endLine + 1).join("\n") + const modifiedContent = targetContent.replace(searchPattern, op.replace) + const modifiedLines = modifiedContent.split("\n") + + // Reconstruct the full content with the modified section + lines = [...beforeLines, ...modifiedLines, ...afterLines] + } else { + // Global replacement + const fullContent = lines.join("\n") + const modifiedContent = fullContent.replace(searchPattern, op.replace) + lines = modifiedContent.split("\n") + } + } + + const newContent = lines.join("\n") + + cline.consecutiveMistakeCount = 0 + + // Show diff preview + const diff = formatResponse.createPrettyPatch(relPath, fileContent, newContent) + + if (!diff) { + pushToolResult(`No changes needed for '${relPath}'`) + return + } + + await cline.diffViewProvider.open(relPath) + await cline.diffViewProvider.update(newContent, true) + cline.diffViewProvider.scrollToFirstDiff() + + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + diff: diff, + } satisfies ClineSayTool) + + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + await cline.diffViewProvider.revertChanges() // cline likely handles closing the diff view + return + } + + const { newProblemsMessage, userEdits, finalContent } = await cline.diffViewProvider.saveChanges() + cline.didEditFile = true // used to determine if we should wait for busy terminal to update before sending api request + if (userEdits) { + await cline.say( + "user_feedback_diff", + JSON.stringify({ + tool: fileExists ? "editedExistingFile" : "newFileCreated", + path: getReadablePath(cline.cwd, relPath), + diff: userEdits, + } satisfies ClineSayTool), + ) + pushToolResult( + `The user made the following updates to your content:\n\n${userEdits}\n\n` + + `The updated content, which includes both your original modifications and the user's edits, has been successfully saved to ${relPath.toPosix()}. Here is the full, updated content of the file, including line numbers:\n\n` + + `\n${addLineNumbers(finalContent || "")}\n\n\n` + + `Please note:\n` + + `1. You do not need to re-write the file with these changes, as they have already been applied.\n` + + `2. Proceed with the task using cline updated file content as the new baseline.\n` + + `3. If the user's edits have addressed part of the task or changed the requirements, adjust your approach accordingly.` + + `${newProblemsMessage}`, + ) + } else { + pushToolResult(`Changes successfully applied to ${relPath.toPosix()}:\n\n${newProblemsMessage}`) + } + await cline.diffViewProvider.reset() + return + } + } catch (error) { + await handleError("applying search and replace", error) + await cline.diffViewProvider.reset() + return + } +} + +function escapeRegExp(string: string): string { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") +} From da2af71106f8feb2e39824e82fd9cbca7356abba Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 01:21:01 -0400 Subject: [PATCH 20/38] Move list_code_definition_names to a tool file (#2097) --- src/core/Cline.ts | 71 +++---------------- src/core/tools/listCodeDefinitionNamesTool.ts | 69 ++++++++++++++++++ 2 files changed, 80 insertions(+), 60 deletions(-) create mode 100644 src/core/tools/listCodeDefinitionNamesTool.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index e5ad82895e..620ff96f9d 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -89,6 +89,7 @@ import { writeToFileTool } from "./tools/writeToFileTool" import { applyDiffTool } from "./tools/applyDiffTool" import { insertContentTool } from "./tools/insertContentTool" import { searchAndReplaceTool } from "./tools/searchAndReplaceTool" +import { listCodeDefinitionNamesTool } from "./tools/listCodeDefinitionNamesTool" export type ToolResponse = string | Array type UserContent = Array @@ -1596,66 +1597,16 @@ export class Cline extends EventEmitter { case "list_files": await listFilesTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - case "list_code_definition_names": { - const relPath: string | undefined = block.params.path - const sharedMessageProps: ClineSayTool = { - tool: "listCodeDefinitionNames", - path: getReadablePath(this.cwd, removeClosingTag("path", relPath)), - } - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: "", - } satisfies ClineSayTool) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!relPath) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("list_code_definition_names", "path"), - ) - break - } - this.consecutiveMistakeCount = 0 - const absolutePath = path.resolve(this.cwd, relPath) - let result: string - try { - const stats = await fs.stat(absolutePath) - if (stats.isFile()) { - const fileResult = await parseSourceCodeDefinitionsForFile( - absolutePath, - this.rooIgnoreController, - ) - result = fileResult ?? "No source code definitions found in this file." - } else if (stats.isDirectory()) { - result = await parseSourceCodeForDefinitionsTopLevel( - absolutePath, - this.rooIgnoreController, - ) - } else { - result = "The specified path is neither a file nor a directory." - } - } catch { - result = `${absolutePath}: does not exist or cannot be accessed.` - } - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: result, - } satisfies ClineSayTool) - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - pushToolResult(result) - break - } - } catch (error) { - await handleError("parsing source code definitions", error) - break - } - } + case "list_code_definition_names": + await listCodeDefinitionNamesTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break case "search_files": { const relDirPath: string | undefined = block.params.path const regex: string | undefined = block.params.regex diff --git a/src/core/tools/listCodeDefinitionNamesTool.ts b/src/core/tools/listCodeDefinitionNamesTool.ts new file mode 100644 index 0000000000..46b8afae2b --- /dev/null +++ b/src/core/tools/listCodeDefinitionNamesTool.ts @@ -0,0 +1,69 @@ +import { ToolUse } from "../assistant-message" +import { HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { Cline } from "../Cline" +import { AskApproval } from "./types" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { getReadablePath } from "../../utils/path" +import path from "path" +import fs from "fs/promises" +import { parseSourceCodeForDefinitionsTopLevel, parseSourceCodeDefinitionsForFile } from "../../services/tree-sitter" + +export async function listCodeDefinitionNamesTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relPath: string | undefined = block.params.path + const sharedMessageProps: ClineSayTool = { + tool: "listCodeDefinitionNames", + path: getReadablePath(cline.cwd, removeClosingTag("path", relPath)), + } + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: "", + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!relPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("list_code_definition_names", "path")) + return + } + cline.consecutiveMistakeCount = 0 + const absolutePath = path.resolve(cline.cwd, relPath) + let result: string + try { + const stats = await fs.stat(absolutePath) + if (stats.isFile()) { + const fileResult = await parseSourceCodeDefinitionsForFile(absolutePath, cline.rooIgnoreController) + result = fileResult ?? "No source code definitions found in cline file." + } else if (stats.isDirectory()) { + result = await parseSourceCodeForDefinitionsTopLevel(absolutePath, cline.rooIgnoreController) + } else { + result = "The specified path is neither a file nor a directory." + } + } catch { + result = `${absolutePath}: does not exist or cannot be accessed.` + } + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: result, + } satisfies ClineSayTool) + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return + } + pushToolResult(result) + return + } + } catch (error) { + await handleError("parsing source code definitions", error) + return + } +} From 9be36ddcf3d42248a9c657ec575456f52c7af8ae Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 01:28:58 -0400 Subject: [PATCH 21/38] Move search_files to a tool file (#2098) --- src/core/Cline.ts | 55 ++---------------------- src/core/tools/searchFilesTool.ts | 69 +++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 52 deletions(-) create mode 100644 src/core/tools/searchFilesTool.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 620ff96f9d..bd82b86bb8 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -90,6 +90,7 @@ import { applyDiffTool } from "./tools/applyDiffTool" import { insertContentTool } from "./tools/insertContentTool" import { searchAndReplaceTool } from "./tools/searchAndReplaceTool" import { listCodeDefinitionNamesTool } from "./tools/listCodeDefinitionNamesTool" +import { searchFilesTool } from "./tools/searchFilesTool" export type ToolResponse = string | Array type UserContent = Array @@ -1608,58 +1609,8 @@ export class Cline extends EventEmitter { ) break case "search_files": { - const relDirPath: string | undefined = block.params.path - const regex: string | undefined = block.params.regex - const filePattern: string | undefined = block.params.file_pattern - const sharedMessageProps: ClineSayTool = { - tool: "searchFiles", - path: getReadablePath(this.cwd, removeClosingTag("path", relDirPath)), - regex: removeClosingTag("regex", regex), - filePattern: removeClosingTag("file_pattern", filePattern), - } - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - ...sharedMessageProps, - content: "", - } satisfies ClineSayTool) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!relDirPath) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("search_files", "path")) - break - } - if (!regex) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("search_files", "regex")) - break - } - this.consecutiveMistakeCount = 0 - const absolutePath = path.resolve(this.cwd, relDirPath) - const results = await regexSearchFiles( - this.cwd, - absolutePath, - regex, - filePattern, - this.rooIgnoreController, - ) - const completeMessage = JSON.stringify({ - ...sharedMessageProps, - content: results, - } satisfies ClineSayTool) - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - pushToolResult(results) - break - } - } catch (error) { - await handleError("searching files", error) - break - } + await searchFilesTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break } case "browser_action": { const action: BrowserAction | undefined = block.params.action as BrowserAction diff --git a/src/core/tools/searchFilesTool.ts b/src/core/tools/searchFilesTool.ts new file mode 100644 index 0000000000..e3659da9a1 --- /dev/null +++ b/src/core/tools/searchFilesTool.ts @@ -0,0 +1,69 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { ClineSayTool } from "../../shared/ExtensionMessage" +import { getReadablePath } from "../../utils/path" +import path from "path" +import { regexSearchFiles } from "../../services/ripgrep" + +export async function searchFilesTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const relDirPath: string | undefined = block.params.path + const regex: string | undefined = block.params.regex + const filePattern: string | undefined = block.params.file_pattern + const sharedMessageProps: ClineSayTool = { + tool: "searchFiles", + path: getReadablePath(cline.cwd, removeClosingTag("path", relDirPath)), + regex: removeClosingTag("regex", regex), + filePattern: removeClosingTag("file_pattern", filePattern), + } + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + ...sharedMessageProps, + content: "", + } satisfies ClineSayTool) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!relDirPath) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("search_files", "path")) + return + } + if (!regex) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("search_files", "regex")) + return + } + cline.consecutiveMistakeCount = 0 + const absolutePath = path.resolve(cline.cwd, relDirPath) + const results = await regexSearchFiles( + cline.cwd, + absolutePath, + regex, + filePattern, + cline.rooIgnoreController, + ) + const completeMessage = JSON.stringify({ + ...sharedMessageProps, + content: results, + } satisfies ClineSayTool) + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return + } + pushToolResult(results) + return + } + } catch (error) { + await handleError("searching files", error) + return + } +} From 38365085d711b5e89246db9ef0b397debfffab5a Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 01:34:50 -0400 Subject: [PATCH 22/38] Move browser_action to a tool file (#2099) --- src/core/Cline.ts | 150 +-------------------------- src/core/tools/browserActionTool.ts | 152 ++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 146 deletions(-) create mode 100644 src/core/tools/browserActionTool.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index bd82b86bb8..e833a988a8 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -91,6 +91,7 @@ import { insertContentTool } from "./tools/insertContentTool" import { searchAndReplaceTool } from "./tools/searchAndReplaceTool" import { listCodeDefinitionNamesTool } from "./tools/listCodeDefinitionNamesTool" import { searchFilesTool } from "./tools/searchFilesTool" +import { browserActionTool } from "./tools/browserActionTool" export type ToolResponse = string | Array type UserContent = Array @@ -140,7 +141,7 @@ export class Cline extends EventEmitter { readonly apiConfiguration: ApiConfiguration api: ApiHandler private urlContentFetcher: UrlContentFetcher - private browserSession: BrowserSession + browserSession: BrowserSession didEditFile: boolean = false customInstructions?: string diffStrategy?: DiffStrategy @@ -1613,151 +1614,8 @@ export class Cline extends EventEmitter { break } case "browser_action": { - const action: BrowserAction | undefined = block.params.action as BrowserAction - const url: string | undefined = block.params.url - const coordinate: string | undefined = block.params.coordinate - const text: string | undefined = block.params.text - if (!action || !browserActions.includes(action)) { - // checking for action to ensure it is complete and valid - if (!block.partial) { - // if the block is complete and we don't have a valid action this is a mistake - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("browser_action", "action")) - await this.browserSession.closeBrowser() - } - break - } - - try { - if (block.partial) { - if (action === "launch") { - await this.ask( - "browser_action_launch", - removeClosingTag("url", url), - block.partial, - ).catch(() => {}) - } else { - await this.say( - "browser_action", - JSON.stringify({ - action: action as BrowserAction, - coordinate: removeClosingTag("coordinate", coordinate), - text: removeClosingTag("text", text), - } satisfies ClineSayBrowserAction), - undefined, - block.partial, - ) - } - break - } else { - // Initialize with empty object to avoid "used before assigned" errors - let browserActionResult: BrowserActionResult = {} - if (action === "launch") { - if (!url) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("browser_action", "url"), - ) - await this.browserSession.closeBrowser() - break - } - this.consecutiveMistakeCount = 0 - const didApprove = await askApproval("browser_action_launch", url) - if (!didApprove) { - break - } - - // NOTE: it's okay that we call this message since the partial inspect_site is finished streaming. The only scenario we have to avoid is sending messages WHILE a partial message exists at the end of the messages array. For example the api_req_finished message would interfere with the partial message, so we needed to remove that. - // await this.say("inspect_site_result", "") // no result, starts the loading spinner waiting for result - await this.say("browser_action_result", "") // starts loading spinner - - await this.browserSession.launchBrowser() - browserActionResult = await this.browserSession.navigateToUrl(url) - } else { - if (action === "click") { - if (!coordinate) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError( - "browser_action", - "coordinate", - ), - ) - await this.browserSession.closeBrowser() - break // can't be within an inner switch - } - } - if (action === "type") { - if (!text) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("browser_action", "text"), - ) - await this.browserSession.closeBrowser() - break - } - } - this.consecutiveMistakeCount = 0 - await this.say( - "browser_action", - JSON.stringify({ - action: action as BrowserAction, - coordinate, - text, - } satisfies ClineSayBrowserAction), - undefined, - false, - ) - switch (action) { - case "click": - browserActionResult = await this.browserSession.click(coordinate!) - break - case "type": - browserActionResult = await this.browserSession.type(text!) - break - case "scroll_down": - browserActionResult = await this.browserSession.scrollDown() - break - case "scroll_up": - browserActionResult = await this.browserSession.scrollUp() - break - case "close": - browserActionResult = await this.browserSession.closeBrowser() - break - } - } - - switch (action) { - case "launch": - case "click": - case "type": - case "scroll_down": - case "scroll_up": - await this.say("browser_action_result", JSON.stringify(browserActionResult)) - pushToolResult( - formatResponse.toolResult( - `The browser action has been executed. The console logs and screenshot have been captured for your analysis.\n\nConsole logs:\n${ - browserActionResult?.logs || "(No new logs)" - }\n\n(REMEMBER: if you need to proceed to using non-\`browser_action\` tools or launch a new browser, you MUST first close this browser. For example, if after analyzing the logs and screenshot you need to edit a file, you must first close the browser before you can use the write_to_file tool.)`, - browserActionResult?.screenshot ? [browserActionResult.screenshot] : [], - ), - ) - break - case "close": - pushToolResult( - formatResponse.toolResult( - `The browser has been closed. You may now proceed to using other tools.`, - ), - ) - break - } - break - } - } catch (error) { - await this.browserSession.closeBrowser() // if any error occurs, the browser session is terminated - await handleError("executing browser action", error) - break - } + await browserActionTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break } case "execute_command": { const command: string | undefined = block.params.command diff --git a/src/core/tools/browserActionTool.ts b/src/core/tools/browserActionTool.ts new file mode 100644 index 0000000000..8a9051070d --- /dev/null +++ b/src/core/tools/browserActionTool.ts @@ -0,0 +1,152 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { + BrowserAction, + BrowserActionResult, + browserActions, + ClineSayBrowserAction, +} from "../../shared/ExtensionMessage" +import { formatResponse } from "../prompts/responses" + +export async function browserActionTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const action: BrowserAction | undefined = block.params.action as BrowserAction + const url: string | undefined = block.params.url + const coordinate: string | undefined = block.params.coordinate + const text: string | undefined = block.params.text + if (!action || !browserActions.includes(action)) { + // checking for action to ensure it is complete and valid + if (!block.partial) { + // if the block is complete and we don't have a valid action cline is a mistake + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "action")) + await cline.browserSession.closeBrowser() + } + return + } + + try { + if (block.partial) { + if (action === "launch") { + await cline.ask("browser_action_launch", removeClosingTag("url", url), block.partial).catch(() => {}) + } else { + await cline.say( + "browser_action", + JSON.stringify({ + action: action as BrowserAction, + coordinate: removeClosingTag("coordinate", coordinate), + text: removeClosingTag("text", text), + } satisfies ClineSayBrowserAction), + undefined, + block.partial, + ) + } + return + } else { + // Initialize with empty object to avoid "used before assigned" errors + let browserActionResult: BrowserActionResult = {} + if (action === "launch") { + if (!url) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "url")) + await cline.browserSession.closeBrowser() + return + } + cline.consecutiveMistakeCount = 0 + const didApprove = await askApproval("browser_action_launch", url) + if (!didApprove) { + return + } + + // NOTE: it's okay that we call cline message since the partial inspect_site is finished streaming. The only scenario we have to avoid is sending messages WHILE a partial message exists at the end of the messages array. For example the api_req_finished message would interfere with the partial message, so we needed to remove that. + // await cline.say("inspect_site_result", "") // no result, starts the loading spinner waiting for result + await cline.say("browser_action_result", "") // starts loading spinner + + await cline.browserSession.launchBrowser() + browserActionResult = await cline.browserSession.navigateToUrl(url) + } else { + if (action === "click") { + if (!coordinate) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "coordinate")) + await cline.browserSession.closeBrowser() + return // can't be within an inner switch + } + } + if (action === "type") { + if (!text) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("browser_action", "text")) + await cline.browserSession.closeBrowser() + return + } + } + cline.consecutiveMistakeCount = 0 + await cline.say( + "browser_action", + JSON.stringify({ + action: action as BrowserAction, + coordinate, + text, + } satisfies ClineSayBrowserAction), + undefined, + false, + ) + switch (action) { + case "click": + browserActionResult = await cline.browserSession.click(coordinate!) + break + case "type": + browserActionResult = await cline.browserSession.type(text!) + break + case "scroll_down": + browserActionResult = await cline.browserSession.scrollDown() + break + case "scroll_up": + browserActionResult = await cline.browserSession.scrollUp() + break + case "close": + browserActionResult = await cline.browserSession.closeBrowser() + break + } + } + + switch (action) { + case "launch": + case "click": + case "type": + case "scroll_down": + case "scroll_up": + await cline.say("browser_action_result", JSON.stringify(browserActionResult)) + pushToolResult( + formatResponse.toolResult( + `The browser action has been executed. The console logs and screenshot have been captured for your analysis.\n\nConsole logs:\n${ + browserActionResult?.logs || "(No new logs)" + }\n\n(REMEMBER: if you need to proceed to using non-\`browser_action\` tools or launch a new browser, you MUST first close cline browser. For example, if after analyzing the logs and screenshot you need to edit a file, you must first close the browser before you can use the write_to_file tool.)`, + browserActionResult?.screenshot ? [browserActionResult.screenshot] : [], + ), + ) + break + case "close": + pushToolResult( + formatResponse.toolResult( + `The browser has been closed. You may now proceed to using other tools.`, + ), + ) + break + } + return + } + } catch (error) { + await cline.browserSession.closeBrowser() // if any error occurs, the browser session is terminated + await handleError("executing browser action", error) + return + } +} From 937b1f3c9003a15de0e0b2e9187f039c6c3d2d5d Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 01:40:16 -0400 Subject: [PATCH 23/38] Move execute_command to a tool file (#2100) --- src/core/Cline.ts | 58 ++++++---------------------- src/core/tools/executeCommandTool.ts | 52 +++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 47 deletions(-) create mode 100644 src/core/tools/executeCommandTool.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index e833a988a8..3b4c683a01 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -92,6 +92,7 @@ import { searchAndReplaceTool } from "./tools/searchAndReplaceTool" import { listCodeDefinitionNamesTool } from "./tools/listCodeDefinitionNamesTool" import { searchFilesTool } from "./tools/searchFilesTool" import { browserActionTool } from "./tools/browserActionTool" +import { executeCommandTool } from "./tools/executeCommandTool" export type ToolResponse = string | Array type UserContent = Array @@ -181,7 +182,7 @@ export class Cline extends EventEmitter { private presentAssistantMessageHasPendingUpdates = false private userMessageContent: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = [] private userMessageContentReady = false - private didRejectTool = false + didRejectTool = false private didAlreadyUseTool = false private didCompleteReadingStream = false @@ -1618,52 +1619,15 @@ export class Cline extends EventEmitter { break } case "execute_command": { - const command: string | undefined = block.params.command - const customCwd: string | undefined = block.params.cwd - try { - if (block.partial) { - await this.ask("command", removeClosingTag("command", command), block.partial).catch( - () => {}, - ) - break - } else { - if (!command) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("execute_command", "command"), - ) - break - } - - const ignoredFileAttemptedToAccess = this.rooIgnoreController?.validateCommand(command) - if (ignoredFileAttemptedToAccess) { - await this.say("rooignore_error", ignoredFileAttemptedToAccess) - pushToolResult( - formatResponse.toolError( - formatResponse.rooIgnoreError(ignoredFileAttemptedToAccess), - ), - ) - - break - } - - this.consecutiveMistakeCount = 0 - - const didApprove = await askApproval("command", command) - if (!didApprove) { - break - } - const [userRejected, result] = await this.executeCommandTool(command, customCwd) - if (userRejected) { - this.didRejectTool = true - } - pushToolResult(result) - break - } - } catch (error) { - await handleError("executing command", error) - break - } + await executeCommandTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break } case "use_mcp_tool": { const server_name: string | undefined = block.params.server_name diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts new file mode 100644 index 0000000000..a0f32dac4d --- /dev/null +++ b/src/core/tools/executeCommandTool.ts @@ -0,0 +1,52 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" + +export async function executeCommandTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const command: string | undefined = block.params.command + const customCwd: string | undefined = block.params.cwd + try { + if (block.partial) { + await cline.ask("command", removeClosingTag("command", command), block.partial).catch(() => {}) + return + } else { + if (!command) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("execute_command", "command")) + return + } + + const ignoredFileAttemptedToAccess = cline.rooIgnoreController?.validateCommand(command) + if (ignoredFileAttemptedToAccess) { + await cline.say("rooignore_error", ignoredFileAttemptedToAccess) + pushToolResult(formatResponse.toolError(formatResponse.rooIgnoreError(ignoredFileAttemptedToAccess))) + + return + } + + cline.consecutiveMistakeCount = 0 + + const didApprove = await askApproval("command", command) + if (!didApprove) { + return + } + const [userRejected, result] = await cline.executeCommandTool(command, customCwd) + if (userRejected) { + cline.didRejectTool = true + } + pushToolResult(result) + return + } + } catch (error) { + await handleError("executing command", error) + return + } +} From 9b0790fce73b14e56b1d4b7a50d898d8b9298a76 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 01:47:19 -0400 Subject: [PATCH 24/38] Move use_mcp_tool and access_mcp_resource to tool files (#2101) --- src/core/Cline.ts | 175 ++---------------------- src/core/tools/accessMcpResourceTool.ts | 77 +++++++++++ src/core/tools/useMcpToolTool.ts | 100 ++++++++++++++ 3 files changed, 190 insertions(+), 162 deletions(-) create mode 100644 src/core/tools/accessMcpResourceTool.ts create mode 100644 src/core/tools/useMcpToolTool.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 3b4c683a01..e7b2694c7d 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -93,6 +93,8 @@ import { listCodeDefinitionNamesTool } from "./tools/listCodeDefinitionNamesTool import { searchFilesTool } from "./tools/searchFilesTool" import { browserActionTool } from "./tools/browserActionTool" import { executeCommandTool } from "./tools/executeCommandTool" +import { useMcpToolTool } from "./tools/useMcpToolTool" +import { accessMcpResourceTool } from "./tools/accessMcpResourceTool" export type ToolResponse = string | Array type UserContent = Array @@ -1630,170 +1632,19 @@ export class Cline extends EventEmitter { break } case "use_mcp_tool": { - const server_name: string | undefined = block.params.server_name - const tool_name: string | undefined = block.params.tool_name - const mcp_arguments: string | undefined = block.params.arguments - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - type: "use_mcp_tool", - serverName: removeClosingTag("server_name", server_name), - toolName: removeClosingTag("tool_name", tool_name), - arguments: removeClosingTag("arguments", mcp_arguments), - } satisfies ClineAskUseMcpServer) - await this.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!server_name) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("use_mcp_tool", "server_name"), - ) - break - } - if (!tool_name) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("use_mcp_tool", "tool_name"), - ) - break - } - // arguments are optional, but if they are provided they must be valid JSON - // if (!mcp_arguments) { - // this.consecutiveMistakeCount++ - // pushToolResult(await this.sayAndCreateMissingParamError("use_mcp_tool", "arguments")) - // break - // } - let parsedArguments: Record | undefined - if (mcp_arguments) { - try { - parsedArguments = JSON.parse(mcp_arguments) - } catch (error) { - this.consecutiveMistakeCount++ - await this.say( - "error", - `Roo tried to use ${tool_name} with an invalid JSON argument. Retrying...`, - ) - pushToolResult( - formatResponse.toolError( - formatResponse.invalidMcpToolArgumentError(server_name, tool_name), - ), - ) - break - } - } - this.consecutiveMistakeCount = 0 - const completeMessage = JSON.stringify({ - type: "use_mcp_tool", - serverName: server_name, - toolName: tool_name, - arguments: mcp_arguments, - } satisfies ClineAskUseMcpServer) - const didApprove = await askApproval("use_mcp_server", completeMessage) - if (!didApprove) { - break - } - // now execute the tool - await this.say("mcp_server_request_started") // same as browser_action_result - const toolResult = await this.providerRef - .deref() - ?.getMcpHub() - ?.callTool(server_name, tool_name, parsedArguments) - - // TODO: add progress indicator and ability to parse images and non-text responses - const toolResultPretty = - (toolResult?.isError ? "Error:\n" : "") + - toolResult?.content - .map((item) => { - if (item.type === "text") { - return item.text - } - if (item.type === "resource") { - const { blob, ...rest } = item.resource - return JSON.stringify(rest, null, 2) - } - return "" - }) - .filter(Boolean) - .join("\n\n") || "(No response)" - await this.say("mcp_server_response", toolResultPretty) - pushToolResult(formatResponse.toolResult(toolResultPretty)) - break - } - } catch (error) { - await handleError("executing MCP tool", error) - break - } + await useMcpToolTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break } case "access_mcp_resource": { - const server_name: string | undefined = block.params.server_name - const uri: string | undefined = block.params.uri - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - type: "access_mcp_resource", - serverName: removeClosingTag("server_name", server_name), - uri: removeClosingTag("uri", uri), - } satisfies ClineAskUseMcpServer) - await this.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!server_name) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("access_mcp_resource", "server_name"), - ) - break - } - if (!uri) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("access_mcp_resource", "uri"), - ) - break - } - this.consecutiveMistakeCount = 0 - const completeMessage = JSON.stringify({ - type: "access_mcp_resource", - serverName: server_name, - uri, - } satisfies ClineAskUseMcpServer) - const didApprove = await askApproval("use_mcp_server", completeMessage) - if (!didApprove) { - break - } - // now execute the tool - await this.say("mcp_server_request_started") - const resourceResult = await this.providerRef - .deref() - ?.getMcpHub() - ?.readResource(server_name, uri) - const resourceResultPretty = - resourceResult?.contents - .map((item) => { - if (item.text) { - return item.text - } - return "" - }) - .filter(Boolean) - .join("\n\n") || "(Empty response)" - - // handle images (image must contain mimetype and blob) - let images: string[] = [] - resourceResult?.contents.forEach((item) => { - if (item.mimeType?.startsWith("image") && item.blob) { - images.push(item.blob) - } - }) - await this.say("mcp_server_response", resourceResultPretty, images) - pushToolResult(formatResponse.toolResult(resourceResultPretty, images)) - break - } - } catch (error) { - await handleError("accessing MCP resource", error) - break - } + await accessMcpResourceTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break } case "ask_followup_question": { const question: string | undefined = block.params.question diff --git a/src/core/tools/accessMcpResourceTool.ts b/src/core/tools/accessMcpResourceTool.ts new file mode 100644 index 0000000000..94bea9062c --- /dev/null +++ b/src/core/tools/accessMcpResourceTool.ts @@ -0,0 +1,77 @@ +import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage" +import { RemoveClosingTag } from "./types" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult } from "./types" +import { Cline } from "../Cline" +import { formatResponse } from "../prompts/responses" + +export async function accessMcpResourceTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const server_name: string | undefined = block.params.server_name + const uri: string | undefined = block.params.uri + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + type: "access_mcp_resource", + serverName: removeClosingTag("server_name", server_name), + uri: removeClosingTag("uri", uri), + } satisfies ClineAskUseMcpServer) + await cline.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!server_name) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("access_mcp_resource", "server_name")) + return + } + if (!uri) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("access_mcp_resource", "uri")) + return + } + cline.consecutiveMistakeCount = 0 + const completeMessage = JSON.stringify({ + type: "access_mcp_resource", + serverName: server_name, + uri, + } satisfies ClineAskUseMcpServer) + const didApprove = await askApproval("use_mcp_server", completeMessage) + if (!didApprove) { + return + } + // now execute the tool + await cline.say("mcp_server_request_started") + const resourceResult = await cline.providerRef.deref()?.getMcpHub()?.readResource(server_name, uri) + const resourceResultPretty = + resourceResult?.contents + .map((item) => { + if (item.text) { + return item.text + } + return "" + }) + .filter(Boolean) + .join("\n\n") || "(Empty response)" + + // handle images (image must contain mimetype and blob) + let images: string[] = [] + resourceResult?.contents.forEach((item) => { + if (item.mimeType?.startsWith("image") && item.blob) { + images.push(item.blob) + } + }) + await cline.say("mcp_server_response", resourceResultPretty, images) + pushToolResult(formatResponse.toolResult(resourceResultPretty, images)) + return + } + } catch (error) { + await handleError("accessing MCP resource", error) + return + } +} diff --git a/src/core/tools/useMcpToolTool.ts b/src/core/tools/useMcpToolTool.ts new file mode 100644 index 0000000000..699f693a13 --- /dev/null +++ b/src/core/tools/useMcpToolTool.ts @@ -0,0 +1,100 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" +import { ClineAskUseMcpServer } from "../../shared/ExtensionMessage" + +export async function useMcpToolTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const server_name: string | undefined = block.params.server_name + const tool_name: string | undefined = block.params.tool_name + const mcp_arguments: string | undefined = block.params.arguments + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + type: "use_mcp_tool", + serverName: removeClosingTag("server_name", server_name), + toolName: removeClosingTag("tool_name", tool_name), + arguments: removeClosingTag("arguments", mcp_arguments), + } satisfies ClineAskUseMcpServer) + await cline.ask("use_mcp_server", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!server_name) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "server_name")) + return + } + if (!tool_name) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "tool_name")) + return + } + // arguments are optional, but if they are provided they must be valid JSON + // if (!mcp_arguments) { + // cline.consecutiveMistakeCount++ + // pushToolResult(await cline.sayAndCreateMissingParamError("use_mcp_tool", "arguments")) + // return + // } + let parsedArguments: Record | undefined + if (mcp_arguments) { + try { + parsedArguments = JSON.parse(mcp_arguments) + } catch (error) { + cline.consecutiveMistakeCount++ + await cline.say("error", `Roo tried to use ${tool_name} with an invalid JSON argument. Retrying...`) + pushToolResult( + formatResponse.toolError(formatResponse.invalidMcpToolArgumentError(server_name, tool_name)), + ) + return + } + } + cline.consecutiveMistakeCount = 0 + const completeMessage = JSON.stringify({ + type: "use_mcp_tool", + serverName: server_name, + toolName: tool_name, + arguments: mcp_arguments, + } satisfies ClineAskUseMcpServer) + const didApprove = await askApproval("use_mcp_server", completeMessage) + if (!didApprove) { + return + } + // now execute the tool + await cline.say("mcp_server_request_started") // same as browser_action_result + const toolResult = await cline.providerRef + .deref() + ?.getMcpHub() + ?.callTool(server_name, tool_name, parsedArguments) + + // TODO: add progress indicator and ability to parse images and non-text responses + const toolResultPretty = + (toolResult?.isError ? "Error:\n" : "") + + toolResult?.content + .map((item) => { + if (item.type === "text") { + return item.text + } + if (item.type === "resource") { + const { blob, ...rest } = item.resource + return JSON.stringify(rest, null, 2) + } + return "" + }) + .filter(Boolean) + .join("\n\n") || "(No response)" + await cline.say("mcp_server_response", toolResultPretty) + pushToolResult(formatResponse.toolResult(toolResultPretty)) + return + } + } catch (error) { + await handleError("executing MCP tool", error) + return + } +} From 253d0fecb2424a74e21f829797aae63a5a0fdb2f Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 01:58:35 -0400 Subject: [PATCH 25/38] Move new_task, switch_mode, attempt_completion, and ask_followup_question to tool files (#2102) --- src/core/Cline.ts | 385 ++-------------------- src/core/tools/askFollowupQuestionTool.ts | 71 ++++ src/core/tools/attemptCompletionTool.ts | 152 +++++++++ src/core/tools/newTaskTool.ts | 90 +++++ src/core/tools/switchModeTool.ts | 75 +++++ src/core/tools/types.ts | 4 + 6 files changed, 424 insertions(+), 353 deletions(-) create mode 100644 src/core/tools/askFollowupQuestionTool.ts create mode 100644 src/core/tools/attemptCompletionTool.ts create mode 100644 src/core/tools/newTaskTool.ts create mode 100644 src/core/tools/switchModeTool.ts diff --git a/src/core/Cline.ts b/src/core/Cline.ts index e7b2694c7d..f7a723f336 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -95,6 +95,10 @@ import { browserActionTool } from "./tools/browserActionTool" import { executeCommandTool } from "./tools/executeCommandTool" import { useMcpToolTool } from "./tools/useMcpToolTool" import { accessMcpResourceTool } from "./tools/accessMcpResourceTool" +import { askFollowupQuestionTool } from "./tools/askFollowupQuestionTool" +import { switchModeTool } from "./tools/switchModeTool" +import { attemptCompletionTool } from "./tools/attemptCompletionTool" +import { newTaskTool } from "./tools/newTaskTool" export type ToolResponse = string | Array type UserContent = Array @@ -137,8 +141,8 @@ export class Cline extends EventEmitter { readonly rootTask: Cline | undefined = undefined readonly parentTask: Cline | undefined = undefined readonly taskNumber: number - private isPaused: boolean = false - private pausedModeSlug: string = defaultModeSlug + isPaused: boolean = false + pausedModeSlug: string = defaultModeSlug private pauseInterval: NodeJS.Timeout | undefined readonly apiConfiguration: ApiConfiguration @@ -182,7 +186,7 @@ export class Cline extends EventEmitter { private assistantMessageContent: AssistantMessageContent[] = [] private presentAssistantMessageLocked = false private presentAssistantMessageHasPendingUpdates = false - private userMessageContent: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = [] + userMessageContent: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = [] private userMessageContentReady = false didRejectTool = false private didAlreadyUseTool = false @@ -379,7 +383,7 @@ export class Cline extends EventEmitter { this.emit("message", { action: "updated", message: partialMessage }) } - private getTokenUsage() { + getTokenUsage() { const usage = getApiMetrics(combineApiRequests(combineCommandSequences(this.clineMessages.slice(1)))) this.emit("taskTokenUsageUpdated", this.taskId, usage) return usage @@ -1647,363 +1651,38 @@ export class Cline extends EventEmitter { break } case "ask_followup_question": { - const question: string | undefined = block.params.question - const follow_up: string | undefined = block.params.follow_up - try { - if (block.partial) { - await this.ask("followup", removeClosingTag("question", question), block.partial).catch( - () => {}, - ) - break - } else { - if (!question) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("ask_followup_question", "question"), - ) - break - } - - type Suggest = { - answer: string - } - - let follow_up_json = { - question, - suggest: [] as Suggest[], - } - - if (follow_up) { - let parsedSuggest: { - suggest: Suggest[] | Suggest - } - - try { - parsedSuggest = parseXml(follow_up, ["suggest"]) as { - suggest: Suggest[] | Suggest - } - } catch (error) { - this.consecutiveMistakeCount++ - await this.say("error", `Failed to parse operations: ${error.message}`) - pushToolResult(formatResponse.toolError("Invalid operations xml format")) - break - } - - const normalizedSuggest = Array.isArray(parsedSuggest?.suggest) - ? parsedSuggest.suggest - : [parsedSuggest?.suggest].filter((sug): sug is Suggest => sug !== undefined) - - follow_up_json.suggest = normalizedSuggest - } - - this.consecutiveMistakeCount = 0 - - const { text, images } = await this.ask( - "followup", - JSON.stringify(follow_up_json), - false, - ) - await this.say("user_feedback", text ?? "", images) - pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) - break - } - } catch (error) { - await handleError("asking question", error) - break - } + await askFollowupQuestionTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + ) + break } case "switch_mode": { - const mode_slug: string | undefined = block.params.mode_slug - const reason: string | undefined = block.params.reason - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - tool: "switchMode", - mode: removeClosingTag("mode_slug", mode_slug), - reason: removeClosingTag("reason", reason), - }) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!mode_slug) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("switch_mode", "mode_slug")) - break - } - this.consecutiveMistakeCount = 0 - - // Verify the mode exists - const targetMode = getModeBySlug( - mode_slug, - (await this.providerRef.deref()?.getState())?.customModes, - ) - if (!targetMode) { - pushToolResult(formatResponse.toolError(`Invalid mode: ${mode_slug}`)) - break - } - - // Check if already in requested mode - const currentMode = - (await this.providerRef.deref()?.getState())?.mode ?? defaultModeSlug - if (currentMode === mode_slug) { - pushToolResult(`Already in ${targetMode.name} mode.`) - break - } - - const completeMessage = JSON.stringify({ - tool: "switchMode", - mode: mode_slug, - reason, - }) - - const didApprove = await askApproval("tool", completeMessage) - if (!didApprove) { - break - } - - // Switch the mode using shared handler - await this.providerRef.deref()?.handleModeSwitch(mode_slug) - pushToolResult( - `Successfully switched from ${getModeBySlug(currentMode)?.name ?? currentMode} mode to ${ - targetMode.name - } mode${reason ? ` because: ${reason}` : ""}.`, - ) - await delay(500) // delay to allow mode change to take effect before next tool is executed - break - } - } catch (error) { - await handleError("switching mode", error) - break - } + await switchModeTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break } case "new_task": { - const mode: string | undefined = block.params.mode - const message: string | undefined = block.params.message - try { - if (block.partial) { - const partialMessage = JSON.stringify({ - tool: "newTask", - mode: removeClosingTag("mode", mode), - message: removeClosingTag("message", message), - }) - await this.ask("tool", partialMessage, block.partial).catch(() => {}) - break - } else { - if (!mode) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("new_task", "mode")) - break - } - if (!message) { - this.consecutiveMistakeCount++ - pushToolResult(await this.sayAndCreateMissingParamError("new_task", "message")) - break - } - this.consecutiveMistakeCount = 0 - - // Verify the mode exists - const targetMode = getModeBySlug( - mode, - (await this.providerRef.deref()?.getState())?.customModes, - ) - if (!targetMode) { - pushToolResult(formatResponse.toolError(`Invalid mode: ${mode}`)) - break - } - - const toolMessage = JSON.stringify({ - tool: "newTask", - mode: targetMode.name, - content: message, - }) - const didApprove = await askApproval("tool", toolMessage) - - if (!didApprove) { - break - } - - const provider = this.providerRef.deref() - - if (!provider) { - break - } - - // Preserve the current mode so we can resume with it later. - this.pausedModeSlug = (await provider.getState()).mode ?? defaultModeSlug - - // Switch mode first, then create new task instance. - await provider.handleModeSwitch(mode) - - // Delay to allow mode change to take effect before next tool is executed. - await delay(500) - - const newCline = await provider.initClineWithTask(message, undefined, this) - this.emit("taskSpawned", newCline.taskId) - - pushToolResult( - `Successfully created new task in ${targetMode.name} mode with message: ${message}`, - ) - - // Set the isPaused flag to true so the parent - // task can wait for the sub-task to finish. - this.isPaused = true - this.emit("taskPaused") - - break - } - } catch (error) { - await handleError("creating new task", error) - break - } + await newTaskTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) + break } case "attempt_completion": { - const result: string | undefined = block.params.result - const command: string | undefined = block.params.command - try { - const lastMessage = this.clineMessages.at(-1) - if (block.partial) { - if (command) { - // the attempt_completion text is done, now we're getting command - // remove the previous partial attempt_completion ask, replace with say, post state to webview, then stream command - - // const secondLastMessage = this.clineMessages.at(-2) - if (lastMessage && lastMessage.ask === "command") { - // update command - await this.ask( - "command", - removeClosingTag("command", command), - block.partial, - ).catch(() => {}) - } else { - // last message is completion_result - // we have command string, which means we have the result as well, so finish it (doesnt have to exist yet) - await this.say( - "completion_result", - removeClosingTag("result", result), - undefined, - false, - ) - - telemetryService.captureTaskCompleted(this.taskId) - this.emit("taskCompleted", this.taskId, this.getTokenUsage()) - - await this.ask( - "command", - removeClosingTag("command", command), - block.partial, - ).catch(() => {}) - } - } else { - // no command, still outputting partial result - await this.say( - "completion_result", - removeClosingTag("result", result), - undefined, - block.partial, - ) - } - break - } else { - if (!result) { - this.consecutiveMistakeCount++ - pushToolResult( - await this.sayAndCreateMissingParamError("attempt_completion", "result"), - ) - break - } - - this.consecutiveMistakeCount = 0 - - let commandResult: ToolResponse | undefined - - if (command) { - if (lastMessage && lastMessage.ask !== "command") { - // Haven't sent a command message yet so first send completion_result then command. - await this.say("completion_result", result, undefined, false) - telemetryService.captureTaskCompleted(this.taskId) - this.emit("taskCompleted", this.taskId, this.getTokenUsage()) - } - - // Complete command message. - const didApprove = await askApproval("command", command) - - if (!didApprove) { - break - } - - const [userRejected, execCommandResult] = await this.executeCommandTool(command!) - - if (userRejected) { - this.didRejectTool = true - pushToolResult(execCommandResult) - break - } - - // User didn't reject, but the command may have output. - commandResult = execCommandResult - } else { - await this.say("completion_result", result, undefined, false) - telemetryService.captureTaskCompleted(this.taskId) - this.emit("taskCompleted", this.taskId, this.getTokenUsage()) - } - - if (this.parentTask) { - 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. - const { response, text, images } = await this.ask("completion_result", "", false) - - // Signals to recursive loop to stop (for now - // this never happens since yesButtonClicked - // will trigger a new task). - if (response === "yesButtonClicked") { - pushToolResult("") - break - } - - await this.say("user_feedback", text ?? "", images) - const toolResults: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = [] - - if (commandResult) { - if (typeof commandResult === "string") { - toolResults.push({ type: "text", text: commandResult }) - } else if (Array.isArray(commandResult)) { - toolResults.push(...commandResult) - } - } - - toolResults.push({ - type: "text", - text: `The user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.\n\n${text}\n`, - }) - - toolResults.push(...formatResponse.imageBlocks(images)) - - this.userMessageContent.push({ - type: "text", - text: `${toolDescription()} Result:`, - }) - - this.userMessageContent.push(...toolResults) - break - } - } catch (error) { - await handleError("inspecting site", error) - break - } + await attemptCompletionTool( + this, + block, + askApproval, + handleError, + pushToolResult, + removeClosingTag, + toolDescription, + askFinishSubTaskApproval, + ) + break } } diff --git a/src/core/tools/askFollowupQuestionTool.ts b/src/core/tools/askFollowupQuestionTool.ts new file mode 100644 index 0000000000..5ed06e2403 --- /dev/null +++ b/src/core/tools/askFollowupQuestionTool.ts @@ -0,0 +1,71 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" +import { parseXml } from "../../utils/xml" + +export async function askFollowupQuestionTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const question: string | undefined = block.params.question + const follow_up: string | undefined = block.params.follow_up + try { + if (block.partial) { + await cline.ask("followup", removeClosingTag("question", question), block.partial).catch(() => {}) + return + } else { + if (!question) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("ask_followup_question", "question")) + return + } + + type Suggest = { + answer: string + } + + let follow_up_json = { + question, + suggest: [] as Suggest[], + } + + if (follow_up) { + let parsedSuggest: { + suggest: Suggest[] | Suggest + } + + try { + parsedSuggest = parseXml(follow_up, ["suggest"]) as { + suggest: Suggest[] | Suggest + } + } catch (error) { + cline.consecutiveMistakeCount++ + await cline.say("error", `Failed to parse operations: ${error.message}`) + pushToolResult(formatResponse.toolError("Invalid operations xml format")) + return + } + + const normalizedSuggest = Array.isArray(parsedSuggest?.suggest) + ? parsedSuggest.suggest + : [parsedSuggest?.suggest].filter((sug): sug is Suggest => sug !== undefined) + + follow_up_json.suggest = normalizedSuggest + } + + cline.consecutiveMistakeCount = 0 + + const { text, images } = await cline.ask("followup", JSON.stringify(follow_up_json), false) + await cline.say("user_feedback", text ?? "", images) + pushToolResult(formatResponse.toolResult(`\n${text}\n`, images)) + return + } + } catch (error) { + await handleError("asking question", error) + return + } +} diff --git a/src/core/tools/attemptCompletionTool.ts b/src/core/tools/attemptCompletionTool.ts new file mode 100644 index 0000000000..6af96e9154 --- /dev/null +++ b/src/core/tools/attemptCompletionTool.ts @@ -0,0 +1,152 @@ +import { ToolResponse } from "../Cline" + +import { ToolUse } from "../assistant-message" +import { Cline } from "../Cline" +import { + AskApproval, + HandleError, + PushToolResult, + RemoveClosingTag, + ToolDescription, + AskFinishSubTaskApproval, +} from "./types" +import { formatResponse } from "../prompts/responses" +import { telemetryService } from "../../services/telemetry/TelemetryService" +import Anthropic from "@anthropic-ai/sdk" + +export async function attemptCompletionTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, + toolDescription: ToolDescription, + askFinishSubTaskApproval: AskFinishSubTaskApproval, +) { + const result: string | undefined = block.params.result + const command: string | undefined = block.params.command + try { + const lastMessage = cline.clineMessages.at(-1) + if (block.partial) { + if (command) { + // the attempt_completion text is done, now we're getting command + // remove the previous partial attempt_completion ask, replace with say, post state to webview, then stream command + + // const secondLastMessage = cline.clineMessages.at(-2) + if (lastMessage && lastMessage.ask === "command") { + // update command + await cline.ask("command", removeClosingTag("command", command), block.partial).catch(() => {}) + } else { + // last message is completion_result + // we have command string, which means we have the result as well, so finish it (doesnt have to exist yet) + await cline.say("completion_result", removeClosingTag("result", result), undefined, false) + + telemetryService.captureTaskCompleted(cline.taskId) + cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage()) + + await cline.ask("command", removeClosingTag("command", command), block.partial).catch(() => {}) + } + } else { + // no command, still outputting partial result + await cline.say("completion_result", removeClosingTag("result", result), undefined, block.partial) + } + return + } else { + if (!result) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("attempt_completion", "result")) + return + } + + cline.consecutiveMistakeCount = 0 + + let commandResult: ToolResponse | undefined + + if (command) { + if (lastMessage && lastMessage.ask !== "command") { + // Haven't sent a command message yet so first send completion_result then command. + await cline.say("completion_result", result, undefined, false) + telemetryService.captureTaskCompleted(cline.taskId) + cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage()) + } + + // Complete command message. + const didApprove = await askApproval("command", command) + + if (!didApprove) { + return + } + + const [userRejected, execCommandResult] = await cline.executeCommandTool(command!) + + if (userRejected) { + cline.didRejectTool = true + pushToolResult(execCommandResult) + return + } + + // User didn't reject, but the command may have output. + commandResult = execCommandResult + } else { + await cline.say("completion_result", result, undefined, false) + telemetryService.captureTaskCompleted(cline.taskId) + cline.emit("taskCompleted", cline.taskId, cline.getTokenUsage()) + } + + if (cline.parentTask) { + const didApprove = await askFinishSubTaskApproval() + + if (!didApprove) { + return + } + + // tell the provider to remove the current subtask and resume the previous task in the stack + await cline.providerRef.deref()?.finishSubTask(`Task complete: ${lastMessage?.text}`) + return + } + + // We already sent completion_result says, an + // empty string asks relinquishes control over + // button and field. + const { response, text, images } = await cline.ask("completion_result", "", false) + + // Signals to recursive loop to stop (for now + // cline never happens since yesButtonClicked + // will trigger a new task). + if (response === "yesButtonClicked") { + pushToolResult("") + return + } + + await cline.say("user_feedback", text ?? "", images) + const toolResults: (Anthropic.TextBlockParam | Anthropic.ImageBlockParam)[] = [] + + if (commandResult) { + if (typeof commandResult === "string") { + toolResults.push({ type: "text", text: commandResult }) + } else if (Array.isArray(commandResult)) { + toolResults.push(...commandResult) + } + } + + toolResults.push({ + type: "text", + text: `The user has provided feedback on the results. Consider their input to continue the task, and then attempt completion again.\n\n${text}\n`, + }) + + toolResults.push(...formatResponse.imageBlocks(images)) + + cline.userMessageContent.push({ + type: "text", + text: `${toolDescription()} Result:`, + }) + + cline.userMessageContent.push(...toolResults) + return + } + } catch (error) { + await handleError("inspecting site", error) + return + } +} diff --git a/src/core/tools/newTaskTool.ts b/src/core/tools/newTaskTool.ts new file mode 100644 index 0000000000..57e290c26b --- /dev/null +++ b/src/core/tools/newTaskTool.ts @@ -0,0 +1,90 @@ +import { ToolUse } from "../assistant-message" +import { HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { Cline } from "../Cline" +import { AskApproval } from "./types" +import { defaultModeSlug, getModeBySlug } from "../../shared/modes" +import { formatResponse } from "../prompts/responses" +import delay from "delay" + +export async function newTaskTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const mode: string | undefined = block.params.mode + const message: string | undefined = block.params.message + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + tool: "newTask", + mode: removeClosingTag("mode", mode), + message: removeClosingTag("message", message), + }) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!mode) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "mode")) + return + } + if (!message) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("new_task", "message")) + return + } + cline.consecutiveMistakeCount = 0 + + // Verify the mode exists + const targetMode = getModeBySlug(mode, (await cline.providerRef.deref()?.getState())?.customModes) + if (!targetMode) { + pushToolResult(formatResponse.toolError(`Invalid mode: ${mode}`)) + return + } + + const toolMessage = JSON.stringify({ + tool: "newTask", + mode: targetMode.name, + content: message, + }) + const didApprove = await askApproval("tool", toolMessage) + + if (!didApprove) { + return + } + + const provider = cline.providerRef.deref() + + if (!provider) { + return + } + + // Preserve the current mode so we can resume with it later. + cline.pausedModeSlug = (await provider.getState()).mode ?? defaultModeSlug + + // Switch mode first, then create new task instance. + await provider.handleModeSwitch(mode) + + // Delay to allow mode change to take effect before next tool is executed. + await delay(500) + + const newCline = await provider.initClineWithTask(message, undefined, cline) + cline.emit("taskSpawned", newCline.taskId) + + pushToolResult(`Successfully created new task in ${targetMode.name} mode with message: ${message}`) + + // Set the isPaused flag to true so the parent + // task can wait for the sub-task to finish. + cline.isPaused = true + cline.emit("taskPaused") + + return + } + } catch (error) { + await handleError("creating new task", error) + return + } +} diff --git a/src/core/tools/switchModeTool.ts b/src/core/tools/switchModeTool.ts new file mode 100644 index 0000000000..48e6e59fe0 --- /dev/null +++ b/src/core/tools/switchModeTool.ts @@ -0,0 +1,75 @@ +import { Cline } from "../Cline" +import { ToolUse } from "../assistant-message" +import { AskApproval, HandleError, PushToolResult, RemoveClosingTag } from "./types" +import { formatResponse } from "../prompts/responses" +import { defaultModeSlug } from "../../shared/modes" +import { getModeBySlug } from "../../shared/modes" +import delay from "delay" + +export async function switchModeTool( + cline: Cline, + block: ToolUse, + askApproval: AskApproval, + handleError: HandleError, + pushToolResult: PushToolResult, + removeClosingTag: RemoveClosingTag, +) { + const mode_slug: string | undefined = block.params.mode_slug + const reason: string | undefined = block.params.reason + try { + if (block.partial) { + const partialMessage = JSON.stringify({ + tool: "switchMode", + mode: removeClosingTag("mode_slug", mode_slug), + reason: removeClosingTag("reason", reason), + }) + await cline.ask("tool", partialMessage, block.partial).catch(() => {}) + return + } else { + if (!mode_slug) { + cline.consecutiveMistakeCount++ + pushToolResult(await cline.sayAndCreateMissingParamError("switch_mode", "mode_slug")) + return + } + cline.consecutiveMistakeCount = 0 + + // Verify the mode exists + const targetMode = getModeBySlug(mode_slug, (await cline.providerRef.deref()?.getState())?.customModes) + if (!targetMode) { + pushToolResult(formatResponse.toolError(`Invalid mode: ${mode_slug}`)) + return + } + + // Check if already in requested mode + const currentMode = (await cline.providerRef.deref()?.getState())?.mode ?? defaultModeSlug + if (currentMode === mode_slug) { + pushToolResult(`Already in ${targetMode.name} mode.`) + return + } + + const completeMessage = JSON.stringify({ + tool: "switchMode", + mode: mode_slug, + reason, + }) + + const didApprove = await askApproval("tool", completeMessage) + if (!didApprove) { + return + } + + // Switch the mode using shared handler + await cline.providerRef.deref()?.handleModeSwitch(mode_slug) + pushToolResult( + `Successfully switched from ${getModeBySlug(currentMode)?.name ?? currentMode} mode to ${ + targetMode.name + } mode${reason ? ` because: ${reason}` : ""}.`, + ) + await delay(500) // delay to allow mode change to take effect before next tool is executed + return + } + } catch (error) { + await handleError("switching mode", error) + return + } +} diff --git a/src/core/tools/types.ts b/src/core/tools/types.ts index 0d21b9e3ee..5b027241f6 100644 --- a/src/core/tools/types.ts +++ b/src/core/tools/types.ts @@ -13,3 +13,7 @@ export type HandleError = (action: string, error: Error) => Promise export type PushToolResult = (content: ToolResponse) => void export type RemoveClosingTag = (tag: ToolParamName, content?: string) => string + +export type AskFinishSubTaskApproval = () => Promise + +export type ToolDescription = () => string From 87b06476bbc054bbae57f907c5146b8ad514067f Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 02:07:06 -0400 Subject: [PATCH 26/38] Do a little code cleanup now that tools are refactored out (#2103) --- src/core/Cline.ts | 50 ++++++++++------------------------------------- 1 file changed, 10 insertions(+), 40 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index f7a723f336..1624ff956f 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -22,13 +22,6 @@ import { RepoPerWorkspaceCheckpointService, } from "../services/checkpoints" import { findToolName, formatContentBlockToMarkdown } from "../integrations/misc/export-markdown" -import { - extractTextFromFile, - addLineNumbers, - stripLineNumbers, - everyLineHasLineNumbers, -} from "../integrations/misc/extract-text" -import { countFileLines } from "../integrations/misc/line-counter" import { fetchInstructionsTool } from "./tools/fetchInstructionsTool" import { listFilesTool } from "./tools/listFilesTool" import { readFileTool } from "./tools/readFileTool" @@ -37,25 +30,17 @@ import { Terminal } from "../integrations/terminal/Terminal" import { TerminalRegistry } from "../integrations/terminal/TerminalRegistry" import { UrlContentFetcher } from "../services/browser/UrlContentFetcher" import { listFiles } from "../services/glob/list-files" -import { regexSearchFiles } from "../services/ripgrep" -import { parseSourceCodeDefinitionsForFile, parseSourceCodeForDefinitionsTopLevel } from "../services/tree-sitter" import { CheckpointStorage } from "../shared/checkpoints" import { ApiConfiguration } from "../shared/api" import { findLastIndex } from "../shared/array" import { combineApiRequests } from "../shared/combineApiRequests" import { combineCommandSequences } from "../shared/combineCommandSequences" import { - BrowserAction, - BrowserActionResult, - browserActions, ClineApiReqCancelReason, ClineApiReqInfo, ClineAsk, - ClineAskUseMcpServer, ClineMessage, ClineSay, - ClineSayBrowserAction, - ClineSayTool, ToolProgressStatus, } from "../shared/ExtensionMessage" import { getApiMetrics } from "../shared/getApiMetrics" @@ -66,8 +51,7 @@ import { defaultModeSlug, getModeBySlug, getFullModeDetails } from "../shared/mo import { EXPERIMENT_IDS, experiments as Experiments, ExperimentId } from "../shared/experiments" import { calculateApiCostAnthropic } from "../utils/cost" import { fileExistsAtPath } from "../utils/fs" -import { isPathOutsideWorkspace } from "../utils/pathUtils" -import { arePathsEqual, getReadablePath } from "../utils/path" +import { arePathsEqual } from "../utils/path" import { parseMentions } from "./mentions" import { RooIgnoreController } from "./ignore/RooIgnoreController" import { AssistantMessageContent, parseAssistantMessage, ToolParamName, ToolUseName } from "./assistant-message" @@ -75,15 +59,12 @@ import { formatResponse } from "./prompts/responses" import { SYSTEM_PROMPT } from "./prompts/system" import { truncateConversationIfNeeded } from "./sliding-window" import { ClineProvider } from "./webview/ClineProvider" -import { detectCodeOmission } from "../integrations/editor/detect-omission" import { BrowserSession } from "../services/browser/BrowserSession" import { formatLanguage } from "../shared/language" import { McpHub } from "../services/mcp/McpHub" import { DiffStrategy, getDiffStrategy } from "./diff/DiffStrategy" -import { insertGroups } from "./diff/insert-groups" import { telemetryService } from "../services/telemetry/TelemetryService" import { validateToolUse, isToolAllowedForMode, ToolName } from "./mode-validator" -import { parseXml } from "../utils/xml" import { getWorkspacePath } from "../utils/path" import { writeToFileTool } from "./tools/writeToFileTool" import { applyDiffTool } from "./tools/applyDiffTool" @@ -1616,15 +1597,13 @@ export class Cline extends EventEmitter { removeClosingTag, ) break - case "search_files": { + case "search_files": await searchFilesTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - } - case "browser_action": { + case "browser_action": await browserActionTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - } - case "execute_command": { + case "execute_command": await executeCommandTool( this, block, @@ -1634,12 +1613,10 @@ export class Cline extends EventEmitter { removeClosingTag, ) break - } - case "use_mcp_tool": { + case "use_mcp_tool": await useMcpToolTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - } - case "access_mcp_resource": { + case "access_mcp_resource": await accessMcpResourceTool( this, block, @@ -1649,8 +1626,7 @@ export class Cline extends EventEmitter { removeClosingTag, ) break - } - case "ask_followup_question": { + case "ask_followup_question": await askFollowupQuestionTool( this, block, @@ -1660,18 +1636,13 @@ export class Cline extends EventEmitter { removeClosingTag, ) break - } - case "switch_mode": { + case "switch_mode": await switchModeTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - } - - case "new_task": { + case "new_task": await newTaskTool(this, block, askApproval, handleError, pushToolResult, removeClosingTag) break - } - - case "attempt_completion": { + case "attempt_completion": await attemptCompletionTool( this, block, @@ -1683,7 +1654,6 @@ export class Cline extends EventEmitter { askFinishSubTaskApproval, ) break - } } break From 2303f67afb37d5f71437d7776210581f28a92aec Mon Sep 17 00:00:00 2001 From: Chris Estreich Date: Sat, 29 Mar 2025 23:08:37 -0700 Subject: [PATCH 27/38] Clean up the way we compute the current diff strategy (#2049) * Clean up the way we compute the current diff strategy * Add changeset --- .changeset/orange-items-remember.md | 5 +++ src/core/Cline.ts | 33 ++++----------- src/core/__tests__/Cline.test.ts | 13 +++++- src/core/config/CustomModesManager.ts | 1 + src/core/diff/DiffStrategy.ts | 33 ++++++++------- src/core/prompts/__tests__/system.test.ts | 12 +++--- src/core/webview/ClineProvider.ts | 33 +++++++++------ src/exports/roo-code.d.ts | 4 +- src/exports/types.ts | 4 +- src/schemas/index.ts | 8 ++-- src/shared/experiments.ts | 12 +++--- .../components/settings/AdvancedSettings.tsx | 41 +++++++++++-------- 12 files changed, 106 insertions(+), 93 deletions(-) create mode 100644 .changeset/orange-items-remember.md diff --git a/.changeset/orange-items-remember.md b/.changeset/orange-items-remember.md new file mode 100644 index 0000000000..180538a200 --- /dev/null +++ b/.changeset/orange-items-remember.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Consolidate logic that computes the current diff strategy diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 1624ff956f..27f8e62356 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -227,11 +227,8 @@ export class Cline extends EventEmitter { telemetryService.captureTaskCreated(this.taskId) } - // Initialize diffStrategy based on current state - this.updateDiffStrategy( - Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.DIFF_STRATEGY), - Experiments.isEnabled(experiments ?? {}, EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE), - ) + // Initialize diffStrategy based on current state. + this.updateDiffStrategy(experiments ?? {}) onCreated?.(this) @@ -266,25 +263,13 @@ export class Cline extends EventEmitter { return getWorkspacePath(path.join(os.homedir(), "Desktop")) } - // Add method to update diffStrategy - async updateDiffStrategy(experimentalDiffStrategy?: boolean, multiSearchReplaceDiffStrategy?: boolean) { - // If not provided, get from current state - if (experimentalDiffStrategy === undefined || multiSearchReplaceDiffStrategy === undefined) { - const { experiments: stateExperimental } = (await this.providerRef.deref()?.getState()) ?? {} - if (experimentalDiffStrategy === undefined) { - experimentalDiffStrategy = stateExperimental?.[EXPERIMENT_IDS.DIFF_STRATEGY] ?? false - } - if (multiSearchReplaceDiffStrategy === undefined) { - multiSearchReplaceDiffStrategy = stateExperimental?.[EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE] ?? false - } - } - - this.diffStrategy = getDiffStrategy( - this.api.getModel().id, - this.fuzzyMatchThreshold, - experimentalDiffStrategy, - multiSearchReplaceDiffStrategy, - ) + // Add method to update diffStrategy. + async updateDiffStrategy(experiments: Partial>) { + this.diffStrategy = getDiffStrategy({ + model: this.api.getModel().id, + experiments, + fuzzyMatchThreshold: this.fuzzyMatchThreshold, + }) } // Storing task to disk for history diff --git a/src/core/__tests__/Cline.test.ts b/src/core/__tests__/Cline.test.ts index 68c3208876..1fa9453c3c 100644 --- a/src/core/__tests__/Cline.test.ts +++ b/src/core/__tests__/Cline.test.ts @@ -304,7 +304,12 @@ describe("Cline", () => { expect(cline.diffEnabled).toBe(true) expect(cline.diffStrategy).toBeDefined() - expect(getDiffStrategySpy).toHaveBeenCalledWith("claude-3-5-sonnet-20241022", 0.9, false, false) + + expect(getDiffStrategySpy).toHaveBeenCalledWith({ + model: "claude-3-5-sonnet-20241022", + experiments: {}, + fuzzyMatchThreshold: 0.9, + }) }) it("should pass default threshold to diff strategy when not provided", async () => { @@ -321,7 +326,11 @@ describe("Cline", () => { expect(cline.diffEnabled).toBe(true) expect(cline.diffStrategy).toBeDefined() - expect(getDiffStrategySpy).toHaveBeenCalledWith("claude-3-5-sonnet-20241022", 1.0, false, false) + expect(getDiffStrategySpy).toHaveBeenCalledWith({ + model: "claude-3-5-sonnet-20241022", + experiments: {}, + fuzzyMatchThreshold: 1.0, + }) }) it("should require either task or historyItem", () => { diff --git a/src/core/config/CustomModesManager.ts b/src/core/config/CustomModesManager.ts index cfbe29dcb9..efa3366aee 100644 --- a/src/core/config/CustomModesManager.ts +++ b/src/core/config/CustomModesManager.ts @@ -19,6 +19,7 @@ export class CustomModesManager { private readonly context: vscode.ExtensionContext, private readonly onUpdate: () => Promise, ) { + // TODO: We really shouldn't have async methods in the constructor. this.watchCustomModesFiles() } diff --git a/src/core/diff/DiffStrategy.ts b/src/core/diff/DiffStrategy.ts index e532aec4b0..fe354196c6 100644 --- a/src/core/diff/DiffStrategy.ts +++ b/src/core/diff/DiffStrategy.ts @@ -1,29 +1,28 @@ import type { DiffStrategy } from "./types" -import { UnifiedDiffStrategy } from "./strategies/unified" import { SearchReplaceDiffStrategy } from "./strategies/search-replace" import { NewUnifiedDiffStrategy } from "./strategies/new-unified" import { MultiSearchReplaceDiffStrategy } from "./strategies/multi-search-replace" +import { EXPERIMENT_IDS, ExperimentId } from "../../shared/experiments" + +export type { DiffStrategy } + /** * Get the appropriate diff strategy for the given model * @param model The name of the model being used (e.g., 'gpt-4', 'claude-3-opus') * @returns The appropriate diff strategy for the model */ -export function getDiffStrategy( - model: string, - fuzzyMatchThreshold?: number, - experimentalDiffStrategy: boolean = false, - multiSearchReplaceDiffStrategy: boolean = false, -): DiffStrategy { - if (experimentalDiffStrategy) { - return new NewUnifiedDiffStrategy(fuzzyMatchThreshold) - } - if (multiSearchReplaceDiffStrategy) { - return new MultiSearchReplaceDiffStrategy(fuzzyMatchThreshold) - } else { - return new SearchReplaceDiffStrategy(fuzzyMatchThreshold) - } +export type DiffStrategyName = "unified" | "multi-search-and-replace" | "search-and-replace" + +type GetDiffStrategyOptions = { + model: string + experiments: Partial> + fuzzyMatchThreshold?: number } -export type { DiffStrategy } -export { UnifiedDiffStrategy, SearchReplaceDiffStrategy } +export const getDiffStrategy = ({ fuzzyMatchThreshold, experiments }: GetDiffStrategyOptions): DiffStrategy => + experiments[EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED] + ? new NewUnifiedDiffStrategy(fuzzyMatchThreshold) + : experiments[EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE] + ? new MultiSearchReplaceDiffStrategy(fuzzyMatchThreshold) + : new SearchReplaceDiffStrategy(fuzzyMatchThreshold) diff --git a/src/core/prompts/__tests__/system.test.ts b/src/core/prompts/__tests__/system.test.ts index 0e9d643923..8fd0046501 100644 --- a/src/core/prompts/__tests__/system.test.ts +++ b/src/core/prompts/__tests__/system.test.ts @@ -171,7 +171,7 @@ describe("SYSTEM_PROMPT", () => { beforeEach(() => { // Reset experiments before each test to ensure they're disabled by default experiments = { - [EXPERIMENT_IDS.SEARCH_AND_REPLACE]: false, + [EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: false, [EXPERIMENT_IDS.INSERT_BLOCK]: false, } }) @@ -482,7 +482,7 @@ describe("SYSTEM_PROMPT", () => { it("should disable experimental tools by default", async () => { // Set experiments to explicitly disable experimental tools const experimentsConfig = { - [EXPERIMENT_IDS.SEARCH_AND_REPLACE]: false, + [EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: false, [EXPERIMENT_IDS.INSERT_BLOCK]: false, } @@ -516,7 +516,7 @@ describe("SYSTEM_PROMPT", () => { it("should enable experimental tools when explicitly enabled", async () => { // Set experiments for testing experimental features const experimentsEnabled = { - [EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true, + [EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: true, [EXPERIMENT_IDS.INSERT_BLOCK]: true, } @@ -552,7 +552,7 @@ describe("SYSTEM_PROMPT", () => { it("should selectively enable experimental tools", async () => { // Set experiments for testing selective enabling const experimentsSelective = { - [EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true, + [EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: true, [EXPERIMENT_IDS.INSERT_BLOCK]: false, } @@ -587,7 +587,7 @@ describe("SYSTEM_PROMPT", () => { it("should list all available editing tools in base instruction", async () => { const experiments = { - [EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true, + [EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: true, [EXPERIMENT_IDS.INSERT_BLOCK]: true, } @@ -615,7 +615,7 @@ describe("SYSTEM_PROMPT", () => { }) it("should provide detailed instructions for each enabled tool", async () => { const experiments = { - [EXPERIMENT_IDS.SEARCH_AND_REPLACE]: true, + [EXPERIMENT_IDS.DIFF_STRATEGY_SEARCH_AND_REPLACE]: true, [EXPERIMENT_IDS.INSERT_BLOCK]: true, } diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 8087898c21..0487d1bb98 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -102,7 +102,7 @@ export class ClineProvider extends EventEmitter implements protected mcpHub?: McpHub // Change from private to protected private latestAnnouncementId = "mar-20-2025-3-10" // update to some unique identifier when we add a new announcement private settingsImportedAt?: number - private contextProxy: ContextProxy + public readonly contextProxy: ContextProxy public readonly providerSettingsManager: ProviderSettingsManager public readonly customModesManager: CustomModesManager @@ -1539,6 +1539,7 @@ export class ClineProvider extends EventEmitter implements t("common:confirmation.just_this_message"), t("common:confirmation.this_and_subsequent"), ) + if ( (answer === t("common:confirmation.just_this_message") || answer === t("common:confirmation.this_and_subsequent")) && @@ -1547,9 +1548,11 @@ export class ClineProvider extends EventEmitter implements message.value ) { const timeCutoff = message.value - 1000 // 1 second buffer before the message to delete + const messageIndex = this.getCurrentCline()!.clineMessages.findIndex( (msg) => msg.ts && msg.ts >= timeCutoff, ) + const apiConversationHistoryIndex = this.getCurrentCline()?.apiConversationHistory.findIndex( (msg) => msg.ts && msg.ts >= timeCutoff, @@ -1570,6 +1573,7 @@ export class ClineProvider extends EventEmitter implements const nextUserMessageIndex = this.getCurrentCline()!.clineMessages.findIndex( (msg) => msg === nextUserMessage, ) + // Keep messages before current message and after next user message await this.getCurrentCline()!.overwriteClineMessages([ ...this.getCurrentCline()!.clineMessages.slice(0, messageIndex), @@ -1981,12 +1985,11 @@ export class ClineProvider extends EventEmitter implements await this.updateGlobalState("experiments", updatedExperiments) - // Update diffStrategy in current Cline instance if it exists - if (message.values[EXPERIMENT_IDS.DIFF_STRATEGY] !== undefined && this.getCurrentCline()) { - await this.getCurrentCline()!.updateDiffStrategy( - Experiments.isEnabled(updatedExperiments, EXPERIMENT_IDS.DIFF_STRATEGY), - Experiments.isEnabled(updatedExperiments, EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE), - ) + const currentCline = this.getCurrentCline() + + // Update diffStrategy in current Cline instance if it exists. + if (message.values[EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED] !== undefined && currentCline) { + await currentCline.updateDiffStrategy(updatedExperiments) } await this.postStateToWebview() @@ -2084,13 +2087,13 @@ export class ClineProvider extends EventEmitter implements language, } = await this.getState() - // Create diffStrategy based on current model and settings - const diffStrategy = getDiffStrategy( - apiConfiguration.apiModelId || apiConfiguration.openRouterModelId || "", + // Create diffStrategy based on current model and settings. + const diffStrategy = getDiffStrategy({ + model: apiConfiguration.apiModelId || apiConfiguration.openRouterModelId || "", + experiments, fuzzyMatchThreshold, - Experiments.isEnabled(experiments, EXPERIMENT_IDS.DIFF_STRATEGY), - Experiments.isEnabled(experiments, EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE), - ) + }) + const cwd = this.cwd const mode = message.mode ?? defaultModeSlug @@ -2146,6 +2149,7 @@ export class ClineProvider extends EventEmitter implements public async handleModeSwitch(newMode: Mode) { // Capture mode switch telemetry event const currentTaskId = this.getCurrentCline()?.taskId + if (currentTaskId) { telemetryService.captureModeSwitch(currentTaskId, newMode) } @@ -2162,8 +2166,10 @@ export class ClineProvider extends EventEmitter implements // If this mode has a saved config, use it if (savedConfigId) { const config = listApiConfig?.find((c) => c.id === savedConfigId) + if (config?.name) { const apiConfig = await this.providerSettingsManager.loadConfig(config.name) + await Promise.all([ this.updateGlobalState("currentApiConfigName", config.name), this.updateApiConfiguration(apiConfig), @@ -2175,6 +2181,7 @@ export class ClineProvider extends EventEmitter implements if (currentApiConfigName) { const config = listApiConfig?.find((c) => c.name === currentApiConfigName) + if (config?.id) { await this.providerSettingsManager.setModeConfig(newMode, config.id) } diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index 2f71c6662e..f8bd27da01 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -252,11 +252,11 @@ type GlobalSettings = { fuzzyMatchThreshold?: number | undefined experiments?: | { - experimentalDiffStrategy: boolean search_and_replace: boolean + experimentalDiffStrategy: boolean + multi_search_and_replace: boolean insert_content: boolean powerSteering: boolean - multi_search_and_replace: boolean } | undefined language?: diff --git a/src/exports/types.ts b/src/exports/types.ts index fb3260d4f0..725a458a49 100644 --- a/src/exports/types.ts +++ b/src/exports/types.ts @@ -255,11 +255,11 @@ type GlobalSettings = { fuzzyMatchThreshold?: number | undefined experiments?: | { - experimentalDiffStrategy: boolean search_and_replace: boolean + experimentalDiffStrategy: boolean + multi_search_and_replace: boolean insert_content: boolean powerSteering: boolean - multi_search_and_replace: boolean } | undefined language?: diff --git a/src/schemas/index.ts b/src/schemas/index.ts index eef9ed3cd7..ff3417b8c7 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -275,11 +275,11 @@ export type CustomSupportPrompts = z.infer */ export const experimentIds = [ - "experimentalDiffStrategy", "search_and_replace", + "experimentalDiffStrategy", + "multi_search_and_replace", "insert_content", "powerSteering", - "multi_search_and_replace", ] as const export const experimentIdsSchema = z.enum(experimentIds) @@ -291,11 +291,11 @@ export type ExperimentId = z.infer */ const experimentsSchema = z.object({ - experimentalDiffStrategy: z.boolean(), search_and_replace: z.boolean(), + experimentalDiffStrategy: z.boolean(), + multi_search_and_replace: z.boolean(), insert_content: z.boolean(), powerSteering: z.boolean(), - multi_search_and_replace: z.boolean(), }) export type Experiments = z.infer diff --git a/src/shared/experiments.ts b/src/shared/experiments.ts index b731863e0b..9d931d5a07 100644 --- a/src/shared/experiments.ts +++ b/src/shared/experiments.ts @@ -4,11 +4,11 @@ import { AssertEqual, Equals, Keys, Values } from "../utils/type-fu" export type { ExperimentId } export const EXPERIMENT_IDS = { - DIFF_STRATEGY: "experimentalDiffStrategy", - SEARCH_AND_REPLACE: "search_and_replace", + DIFF_STRATEGY_SEARCH_AND_REPLACE: "search_and_replace", + DIFF_STRATEGY_UNIFIED: "experimentalDiffStrategy", + DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE: "multi_search_and_replace", INSERT_BLOCK: "insert_content", POWER_STEERING: "powerSteering", - MULTI_SEARCH_AND_REPLACE: "multi_search_and_replace", } as const satisfies Record type _AssertExperimentIds = AssertEqual>> @@ -20,11 +20,11 @@ interface ExperimentConfig { } export const experimentConfigsMap: Record = { - DIFF_STRATEGY: { enabled: false }, - SEARCH_AND_REPLACE: { enabled: false }, + DIFF_STRATEGY_SEARCH_AND_REPLACE: { enabled: false }, + DIFF_STRATEGY_UNIFIED: { enabled: false }, + DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE: { enabled: false }, INSERT_BLOCK: { enabled: false }, POWER_STEERING: { enabled: false }, - MULTI_SEARCH_AND_REPLACE: { enabled: false }, } export const experimentDefault = Object.fromEntries( diff --git a/webview-ui/src/components/settings/AdvancedSettings.tsx b/webview-ui/src/components/settings/AdvancedSettings.tsx index e0a909a373..a54386ad30 100644 --- a/webview-ui/src/components/settings/AdvancedSettings.tsx +++ b/webview-ui/src/components/settings/AdvancedSettings.tsx @@ -68,8 +68,8 @@ export const AdvancedSettings = ({ setCachedStateField("diffEnabled", e.target.checked) if (!e.target.checked) { // Reset both experimental strategies when diffs are disabled. - setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY, false) - setExperimentEnabled(EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE, false) + setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED, false) + setExperimentEnabled(EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE, false) } }}> {t("settings:advanced.diff.label")} @@ -87,22 +87,31 @@ export const AdvancedSettings = ({
- {!experiments[EXPERIMENT_IDS.DIFF_STRATEGY] && - !experiments[EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE] && - t("settings:advanced.diff.strategy.descriptions.standard")} - {experiments[EXPERIMENT_IDS.DIFF_STRATEGY] && - t("settings:advanced.diff.strategy.descriptions.unified")} - {experiments[EXPERIMENT_IDS.MULTI_SEARCH_AND_REPLACE] && - t("settings:advanced.diff.strategy.descriptions.multiBlock")} + {experiments[EXPERIMENT_IDS.DIFF_STRATEGY_UNIFIED] + ? t("settings:advanced.diff.strategy.descriptions.unified") + : experiments[EXPERIMENT_IDS.DIFF_STRATEGY_MULTI_SEARCH_AND_REPLACE] + ? t("settings:advanced.diff.strategy.descriptions.multiBlock") + : t("settings:advanced.diff.strategy.descriptions.standard")}
From cf487fbfae55a128bfdb938f274b370ae29e00d5 Mon Sep 17 00:00:00 2001 From: feifei <46489071+feifei325@users.noreply.github.com> Date: Sun, 30 Mar 2025 23:42:48 +0800 Subject: [PATCH 28/38] i18n Add translations for task pinning and unpinning (#2109) Signed-off-by: feifei --- scripts/find-missing-i18n-key.js | 82 +++++++++++++------ src/core/webview/ClineProvider.ts | 2 +- .../webview/__tests__/ClineProvider.test.ts | 2 +- webview-ui/src/i18n/locales/ca/chat.json | 2 + webview-ui/src/i18n/locales/de/chat.json | 2 + webview-ui/src/i18n/locales/en/chat.json | 2 + webview-ui/src/i18n/locales/es/chat.json | 2 + webview-ui/src/i18n/locales/fr/chat.json | 2 + webview-ui/src/i18n/locales/hi/chat.json | 2 + webview-ui/src/i18n/locales/it/chat.json | 2 + webview-ui/src/i18n/locales/ja/chat.json | 2 + webview-ui/src/i18n/locales/ko/chat.json | 2 + webview-ui/src/i18n/locales/pl/chat.json | 2 + webview-ui/src/i18n/locales/pt-BR/chat.json | 2 + webview-ui/src/i18n/locales/tr/chat.json | 2 + webview-ui/src/i18n/locales/vi/chat.json | 2 + webview-ui/src/i18n/locales/zh-CN/chat.json | 2 + webview-ui/src/i18n/locales/zh-TW/chat.json | 2 + 18 files changed, 87 insertions(+), 29 deletions(-) diff --git a/scripts/find-missing-i18n-key.js b/scripts/find-missing-i18n-key.js index 3c21dfb410..87d160d490 100644 --- a/scripts/find-missing-i18n-key.js +++ b/scripts/find-missing-i18n-key.js @@ -34,9 +34,17 @@ Output: process.exit(0) } -// Directory to traverse -const TARGET_DIR = path.join(__dirname, "../webview-ui/src/components") -const LOCALES_DIR = path.join(__dirname, "../webview-ui/src/i18n/locales") +// Directories to traverse and their corresponding locales +const DIRS = { + components: { + path: path.join(__dirname, "../webview-ui/src/components"), + localesDir: path.join(__dirname, "../webview-ui/src/i18n/locales"), + }, + src: { + path: path.join(__dirname, "../src"), + localesDir: path.join(__dirname, "../src/i18n/locales"), + }, +} // Regular expressions to match i18n keys const i18nPatterns = [ @@ -45,15 +53,23 @@ const i18nPatterns = [ /t\("([a-zA-Z][a-zA-Z0-9_]*[:.][a-zA-Z0-9_.]+)"\)/g, // Match t("key") format, where key contains a colon or dot ] -// Get all language directories -function getLocaleDirs() { - const allLocales = fs.readdirSync(LOCALES_DIR).filter((file) => { - const stats = fs.statSync(path.join(LOCALES_DIR, file)) - return stats.isDirectory() // Do not exclude any language directories - }) +// Get all language directories for a specific locales directory +function getLocaleDirs(localesDir) { + try { + const allLocales = fs.readdirSync(localesDir).filter((file) => { + const stats = fs.statSync(path.join(localesDir, file)) + return stats.isDirectory() // Do not exclude any language directories + }) - // Filter to a specific language if specified - return args.locale ? allLocales.filter((locale) => locale === args.locale) : allLocales + // Filter to a specific language if specified + return args.locale ? allLocales.filter((locale) => locale === args.locale) : allLocales + } catch (error) { + if (error.code === "ENOENT") { + console.warn(`Warning: Locales directory not found: ${localesDir}`) + return [] + } + throw error + } } // Get the value from JSON by path @@ -72,14 +88,14 @@ function getValueByPath(obj, path) { } // Check if the key exists in all language files, return a list of missing language files -function checkKeyInLocales(key, localeDirs) { +function checkKeyInLocales(key, localeDirs, localesDir) { const [file, ...pathParts] = key.split(":") const jsonPath = pathParts.join(".") const missingLocales = [] localeDirs.forEach((locale) => { - const filePath = path.join(LOCALES_DIR, locale, `${file}.json`) + const filePath = path.join(localesDir, locale, `${file}.json`) if (!fs.existsSync(filePath)) { missingLocales.push(`${locale}/${file}.json`) return @@ -96,21 +112,20 @@ function checkKeyInLocales(key, localeDirs) { // Recursively traverse the directory function findMissingI18nKeys() { - const localeDirs = getLocaleDirs() const results = [] - function walk(dir) { + function walk(dir, baseDir, localeDirs, localesDir) { const files = fs.readdirSync(dir) for (const file of files) { const filePath = path.join(dir, file) const stat = fs.statSync(filePath) - // Exclude test files - if (filePath.includes(".test.")) continue + // Exclude test files and __mocks__ directory + if (filePath.includes(".test.") || filePath.includes("__mocks__")) continue if (stat.isDirectory()) { - walk(filePath) // Recursively traverse subdirectories + walk(filePath, baseDir, localeDirs, localesDir) // Recursively traverse subdirectories } else if (stat.isFile() && [".ts", ".tsx", ".js", ".jsx"].includes(path.extname(filePath))) { const content = fs.readFileSync(filePath, "utf8") @@ -119,12 +134,12 @@ function findMissingI18nKeys() { let match while ((match = pattern.exec(content)) !== null) { const key = match[1] - const missingLocales = checkKeyInLocales(key, localeDirs) + const missingLocales = checkKeyInLocales(key, localeDirs, localesDir) if (missingLocales.length > 0) { results.push({ key, missingLocales, - file: path.relative(TARGET_DIR, filePath), + file: path.relative(baseDir, filePath), }) } } @@ -133,20 +148,33 @@ function findMissingI18nKeys() { } } - walk(TARGET_DIR) + // Walk through all directories + Object.entries(DIRS).forEach(([name, config]) => { + const localeDirs = getLocaleDirs(config.localesDir) + if (localeDirs.length > 0) { + console.log(`\nChecking ${name} directory with ${localeDirs.length} languages: ${localeDirs.join(", ")}`) + walk(config.path, config.path, localeDirs, config.localesDir) + } + }) + return results } // Execute and output the results function main() { try { - const localeDirs = getLocaleDirs() - if (args.locale && localeDirs.length === 0) { - console.error(`Error: Language '${args.locale}' not found in ${LOCALES_DIR}`) - process.exit(1) - } + if (args.locale) { + // Check if the specified locale exists in any of the locales directories + const localeExists = Object.values(DIRS).some((config) => { + const localeDirs = getLocaleDirs(config.localesDir) + return localeDirs.includes(args.locale) + }) - console.log(`Checking ${localeDirs.length} non-English languages: ${localeDirs.join(", ")}`) + if (!localeExists) { + console.error(`Error: Language '${args.locale}' not found in any locales directory`) + process.exit(1) + } + } const missingKeys = findMissingI18nKeys() diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 0487d1bb98..f0027b322a 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1250,7 +1250,7 @@ export class ClineProvider extends EventEmitter implements } case "openProjectMcpSettings": { if (!vscode.workspace.workspaceFolders?.length) { - vscode.window.showErrorMessage(t("common:no_workspace")) + vscode.window.showErrorMessage(t("common:errors.no_workspace")) return } diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index ea0677b010..059dca72de 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -2031,7 +2031,7 @@ describe("Project MCP Settings", () => { await messageHandler({ type: "openProjectMcpSettings" }) // Verify error message was shown - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("no_workspace") + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("errors.no_workspace") }) test.skip("handles openProjectMcpSettings file creation error", async () => { diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 1d29d269fd..7225e45c59 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -12,6 +12,8 @@ "export": "Exportar historial de tasques", "delete": "Eliminar tasca (Shift + Clic per ometre confirmació)" }, + "unpin": "desancorar", + "pin": "ancorar", "tokenProgress": { "availableSpace": "Espai disponible: {{amount}} tokens", "tokensUsed": "Tokens utilitzats: {{used}} de {{total}}", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 5dd572796d..5aa623810c 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -12,6 +12,8 @@ "export": "Aufgabenverlauf exportieren", "delete": "Aufgabe löschen (Shift + Klick zum Überspringen der Bestätigung)" }, + "unpin": "lösen", + "pin": "anheften", "tokenProgress": { "availableSpace": "Verfügbarer Speicher: {{amount}} Tokens", "tokensUsed": "Verwendete Tokens: {{used}} von {{total}}", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 3e3e5ecfac..0fa6520ec7 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -12,6 +12,8 @@ "export": "Export task history", "delete": "Delete Task (Shift + Click to skip confirmation)" }, + "unpin": "unpin", + "pin": "pin", "retry": { "title": "Retry", "tooltip": "Try the operation again" diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index fdaa5a69b0..ddb09122aa 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -12,6 +12,8 @@ "export": "Exportar historial de tareas", "delete": "Eliminar tarea (Shift + Clic para omitir confirmación)" }, + "unpin": "desanclar", + "pin": "anclar", "retry": { "title": "Reintentar", "tooltip": "Intenta la operación de nuevo" diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 5aa3490b9a..6577f275ce 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -12,6 +12,8 @@ "export": "Exporter l'historique des tâches", "delete": "Supprimer la tâche (Shift + Clic pour ignorer la confirmation)" }, + "unpin": "détacher", + "pin": "épingler", "tokenProgress": { "availableSpace": "Espace disponible : {{amount}} tokens", "tokensUsed": "Tokens utilisés : {{used}} sur {{total}}", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 0d00d2fd8d..2b85828b08 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -12,6 +12,8 @@ "export": "कार्य इतिहास निर्यात करें", "delete": "कार्य हटाएं (पुष्टि को छोड़ने के लिए Shift + क्लिक)" }, + "unpin": "पिन हटाएं", + "pin": "पिन करें", "tokenProgress": { "availableSpace": "उपलब्ध स्थान: {{amount}} tokens", "tokensUsed": "प्रयुक्त tokens: {{used}} / {{total}}", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index e21a82da0d..d14ff9d051 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -12,6 +12,8 @@ "export": "Esporta cronologia attività", "delete": "Elimina attività (Shift + Clic per saltare la conferma)" }, + "unpin": "sblocca", + "pin": "blocca", "tokenProgress": { "availableSpace": "Spazio disponibile: {{amount}} tokens", "tokensUsed": "Tokens utilizzati: {{used}} di {{total}}", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 9b82e56001..7f4fdd7e32 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -12,6 +12,8 @@ "export": "タスク履歴をエクスポート", "delete": "タスクを削除(Shift + クリックで確認をスキップ)" }, + "unpin": "固定解除", + "pin": "固定", "tokenProgress": { "availableSpace": "利用可能な空き容量: {{amount}} トークン", "tokensUsed": "使用トークン: {{used}} / {{total}}", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index c1b288e39b..4aa5960b6a 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -12,6 +12,8 @@ "export": "작업 기록 내보내기", "delete": "작업 삭제 (Shift + 클릭으로 확인 생략)" }, + "unpin": "고정 해제", + "pin": "고정", "tokenProgress": { "availableSpace": "사용 가능한 공간: {{amount}} 토큰", "tokensUsed": "사용된 토큰: {{used}} / {{total}}", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 846534c559..8157d91db0 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -12,6 +12,8 @@ "export": "Eksportuj historię zadań", "delete": "Usuń zadanie (Shift + Kliknięcie, aby pominąć potwierdzenie)" }, + "unpin": "odepnij", + "pin": "przypnij", "tokenProgress": { "availableSpace": "Dostępne miejsce: {{amount}} tokenów", "tokensUsed": "Wykorzystane tokeny: {{used}} z {{total}}", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 560e9b9bff..5c52c8bea9 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -12,6 +12,8 @@ "export": "Exportar histórico de tarefas", "delete": "Excluir tarefa (Shift + Clique para pular confirmação)" }, + "unpin": "desafixar", + "pin": "fixar", "tokenProgress": { "availableSpace": "Espaço disponível: {{amount}} tokens", "tokensUsed": "Tokens usados: {{used}} de {{total}}", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index b415524f92..d14b7a3ee2 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -12,6 +12,8 @@ "export": "Görev geçmişini dışa aktar", "delete": "Görevi sil (Onayı atlamak için Shift + Tıkla)" }, + "unpin": "sabitlemeyi kaldır", + "pin": "sabitle", "tokenProgress": { "availableSpace": "Kullanılabilir alan: {{amount}} token", "tokensUsed": "Kullanılan tokenlar: {{used}} / {{total}}", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index f0f3221069..eabc7bb746 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -12,6 +12,8 @@ "export": "Xuất lịch sử nhiệm vụ", "delete": "Xóa nhiệm vụ (Shift + Click để bỏ qua xác nhận)" }, + "unpin": "bỏ ghim", + "pin": "ghim", "tokenProgress": { "availableSpace": "Không gian khả dụng: {{amount}} tokens", "tokensUsed": "Tokens đã sử dụng: {{used}} trong {{total}}", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index be20601e9b..7d6205dc70 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -12,6 +12,8 @@ "export": "导出任务历史", "delete": "删除任务(Shift + 点击跳过确认)" }, + "unpin": "取消置顶", + "pin": "置顶", "tokenProgress": { "availableSpace": "可用空间: {{amount}} tokens", "tokensUsed": "已使用tokens: {{used}} / {{total}}", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 1e22877355..bfd69270e8 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -12,6 +12,8 @@ "export": "匯出任務歷史", "delete": "刪除任務(Shift + 點擊跳過確認)" }, + "unpin": "取消置頂", + "pin": "置頂", "tokenProgress": { "availableSpace": "可用空間: {{amount}} tokens", "tokensUsed": "已使用tokens: {{used}} / {{total}}", From 712ca71ee037daf20e0f6d834c356e2311f5f36f Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 12:04:34 -0400 Subject: [PATCH 29/38] Remove the ask promise error (#2107) * Remove the ask promise error * Send start_time along with checkpoints and use it to position in the messages --- src/core/Cline.ts | 49 ++++++++----------- .../checkpoints/ShadowCheckpointService.ts | 2 +- src/services/checkpoints/types.ts | 1 + 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 27f8e62356..f9458c7b6c 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -142,7 +142,6 @@ export class Cline extends EventEmitter { private askResponse?: ClineAskResponse private askResponseText?: string private askResponseImages?: string[] - private lastMessageTs?: number // Not private since it needs to be accessible by tools consecutiveMistakeCount: number = 0 consecutiveMistakeCountForApplyDiff: Map = new Map() @@ -333,7 +332,17 @@ export class Cline extends EventEmitter { } private async addToClineMessages(message: ClineMessage) { - this.clineMessages.push(message) + // Find the correct position to insert the message based on timestamp + const insertIndex = this.clineMessages.findIndex((existingMsg) => existingMsg.ts > message.ts) + + if (insertIndex === -1) { + // If no message with a later timestamp is found, append to the end + this.clineMessages.push(message) + } else { + // Insert the message at the correct position to maintain chronological order + this.clineMessages.splice(insertIndex, 0, message) + } + await this.providerRef.deref()?.postStateToWebview() this.emit("message", { action: "created", message }) await this.saveClineMessages() @@ -441,7 +450,6 @@ export class Cline extends EventEmitter { // This is a new partial message, so add it with partial // state. askTs = Date.now() - this.lastMessageTs = askTs await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, partial }) throw new Error("Current ask promise was ignored (#2)") } @@ -460,8 +468,6 @@ export class Cline extends EventEmitter { So in this case we must make sure that the message ts is never altered after first setting it. */ askTs = lastMessage.ts - this.lastMessageTs = askTs - // lastMessage.ts = askTs lastMessage.text = text lastMessage.partial = false lastMessage.progressStatus = progressStatus @@ -473,7 +479,6 @@ export class Cline extends EventEmitter { this.askResponseText = undefined this.askResponseImages = undefined askTs = Date.now() - this.lastMessageTs = askTs await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text }) } } @@ -483,18 +488,10 @@ export class Cline extends EventEmitter { this.askResponseText = undefined this.askResponseImages = undefined askTs = Date.now() - this.lastMessageTs = askTs await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text }) } - await pWaitFor(() => this.askResponse !== undefined || this.lastMessageTs !== askTs, { interval: 100 }) - - if (this.lastMessageTs !== askTs) { - // Could happen if we send multiple asks in a row i.e. with - // command_output. It's important that when we know an ask could - // fail, it is handled gracefully. - throw new Error("Current ask promise was ignored") - } + await pWaitFor(() => this.askResponse !== undefined, { interval: 100 }) const result = { response: this.askResponse!, text: this.askResponseText, images: this.askResponseImages } this.askResponse = undefined @@ -522,6 +519,8 @@ export class Cline extends EventEmitter { throw new Error(`[Cline#say] task ${this.taskId}.${this.instanceId} aborted`) } + const sayTs = (checkpoint?.startTime as number) ?? Date.now() + if (partial !== undefined) { const lastMessage = this.clineMessages.at(-1) const isUpdatingPreviousPartial = @@ -536,8 +535,6 @@ export class Cline extends EventEmitter { this.updateClineMessage(lastMessage) } else { // this is a new partial message, so add it with partial state - const sayTs = Date.now() - this.lastMessageTs = sayTs await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, partial }) } } else { @@ -545,8 +542,6 @@ export class Cline extends EventEmitter { if (isUpdatingPreviousPartial) { // This is the complete version of a previously partial // message, so replace the partial with the complete version. - this.lastMessageTs = lastMessage.ts - // lastMessage.ts = sayTs lastMessage.text = text lastMessage.images = images lastMessage.partial = false @@ -558,15 +553,11 @@ export class Cline extends EventEmitter { this.updateClineMessage(lastMessage) } else { // This is a new and complete message, so add it like normal. - const sayTs = Date.now() - this.lastMessageTs = sayTs await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images }) } } } else { // this is a new non-partial message, so add it like normal - const sayTs = Date.now() - this.lastMessageTs = sayTs await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, checkpoint }) } } @@ -2403,14 +2394,16 @@ export class Cline extends EventEmitter { } }) - service.on("checkpoint", ({ isFirst, fromHash: from, toHash: to }) => { + service.on("checkpoint", ({ isFirst, fromHash: from, toHash: to, startTime }) => { try { this.providerRef.deref()?.postMessageToWebview({ type: "currentCheckpointUpdated", text: to }) - this.say("checkpoint_saved", to, undefined, undefined, { isFirst, from, to }).catch((err) => { - log("[Cline#initializeCheckpoints] caught unexpected error in say('checkpoint_saved')") - console.error(err) - }) + this.say("checkpoint_saved", to, undefined, undefined, { isFirst, from, to, startTime }).catch( + (err) => { + log("[Cline#initializeCheckpoints] caught unexpected error in say('checkpoint_saved')") + console.error(err) + }, + ) } catch (err) { log( "[Cline#initializeCheckpoints] caught unexpected error in on('checkpoint'), disabling checkpoints", diff --git a/src/services/checkpoints/ShadowCheckpointService.ts b/src/services/checkpoints/ShadowCheckpointService.ts index fc7153bab9..85d0c27279 100644 --- a/src/services/checkpoints/ShadowCheckpointService.ts +++ b/src/services/checkpoints/ShadowCheckpointService.ts @@ -218,7 +218,7 @@ export abstract class ShadowCheckpointService extends EventEmitter { const duration = Date.now() - startTime if (isFirst || result.commit) { - this.emit("checkpoint", { type: "checkpoint", isFirst, fromHash, toHash, duration }) + this.emit("checkpoint", { type: "checkpoint", isFirst, fromHash, toHash, duration, startTime }) } if (result.commit) { diff --git a/src/services/checkpoints/types.ts b/src/services/checkpoints/types.ts index 81611e81ec..e3e96c9119 100644 --- a/src/services/checkpoints/types.ts +++ b/src/services/checkpoints/types.ts @@ -29,6 +29,7 @@ export interface CheckpointEventMap { fromHash: string toHash: string duration: number + startTime: number } restore: { type: "restore"; commitHash: string; duration: number } error: { type: "error"; error: Error } From 37ecf9648b3fb859c46699fb44a93bb0747ce979 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 12:22:53 -0400 Subject: [PATCH 30/38] Link to the settings page from the auto approve toolbar (#2111) --- src/i18n/setup.ts | 1 - webview-ui/src/components/chat/AutoApproveMenu.tsx | 13 ++++++++++++- webview-ui/src/i18n/locales/ca/chat.json | 2 +- webview-ui/src/i18n/locales/de/chat.json | 2 +- webview-ui/src/i18n/locales/en/chat.json | 2 +- webview-ui/src/i18n/locales/es/chat.json | 2 +- webview-ui/src/i18n/locales/fr/chat.json | 2 +- webview-ui/src/i18n/locales/hi/chat.json | 2 +- webview-ui/src/i18n/locales/it/chat.json | 2 +- webview-ui/src/i18n/locales/ja/chat.json | 2 +- webview-ui/src/i18n/locales/ko/chat.json | 2 +- webview-ui/src/i18n/locales/pl/chat.json | 2 +- webview-ui/src/i18n/locales/pt-BR/chat.json | 2 +- webview-ui/src/i18n/locales/tr/chat.json | 2 +- webview-ui/src/i18n/locales/vi/chat.json | 2 +- webview-ui/src/i18n/locales/zh-CN/chat.json | 2 +- webview-ui/src/i18n/locales/zh-TW/chat.json | 2 +- 17 files changed, 27 insertions(+), 17 deletions(-) diff --git a/src/i18n/setup.ts b/src/i18n/setup.ts index dc5fb7b99d..058f357b46 100644 --- a/src/i18n/setup.ts +++ b/src/i18n/setup.ts @@ -55,7 +55,6 @@ if (!isTestEnv) { // Read and parse the JSON file const content = fs.readFileSync(filePath, "utf8") translations[language][namespace] = JSON.parse(content) - console.log(`Successfully loaded '${language}/${namespace}' translations`) } catch (error) { console.error(`Error loading translation file ${filePath}:`, error) } diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx index b3a55c94ec..47565a36d6 100644 --- a/webview-ui/src/components/chat/AutoApproveMenu.tsx +++ b/webview-ui/src/components/chat/AutoApproveMenu.tsx @@ -2,6 +2,8 @@ import { VSCodeCheckbox } from "@vscode/webview-ui-toolkit/react" import { useCallback, useState } from "react" import { useExtensionState } from "../../context/ExtensionStateContext" import { useAppTranslation } from "../../i18n/TranslationContext" +import { Trans } from "react-i18next" +import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" import { vscode } from "../../utils/vscode" interface AutoApproveAction { @@ -158,6 +160,10 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { vscode.postMessage({ type: "alwaysApproveResubmit", bool: newValue }) }, [alwaysApproveResubmit, setAlwaysApproveResubmit]) + const handleOpenSettings = useCallback(() => { + window.postMessage({ type: "action", action: "settingsButtonClicked" }) + }, []) + // Map action IDs to their specific handlers const actionHandlers: Record void> = { readFiles: handleReadOnlyChange, @@ -243,7 +249,12 @@ const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { color: "var(--vscode-descriptionForeground)", fontSize: "12px", }}> - {t("chat:autoApprove.description")} + , + }} + /> {actions.map((action) => (
diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 7225e45c59..1fa9509bc9 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "Aprovació automàtica:", "none": "Cap", - "description": "L'aprovació automàtica permet a Roo Code realitzar accions sense demanar permís. Activa-la només per a accions en les que confies plenament.", + "description": "L'aprovació automàtica permet a Roo Code realitzar accions sense demanar permís. Activa-la només per a accions en les que confies plenament. Configuració més detallada disponible a la Configuració.", "actions": { "readFiles": { "label": "Llegir fitxers i directoris", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 5aa623810c..3255160a74 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "Automatische Genehmigung:", "none": "Keine", - "description": "Automatische Genehmigung erlaubt Roo Code, Aktionen ohne Nachfrage auszuführen. Aktiviere dies nur für Aktionen, denen du vollständig vertraust.", + "description": "Automatische Genehmigung erlaubt Roo Code, Aktionen ohne Nachfrage auszuführen. Aktiviere dies nur für Aktionen, denen du vollständig vertraust. Detailliertere Konfiguration verfügbar in den Einstellungen.", "actions": { "readFiles": { "label": "Dateien und Verzeichnisse lesen", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 0fa6520ec7..38fb8dfe6d 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "Auto-approve:", "none": "None", - "description": "Auto-approve allows Roo Code to perform actions without asking for permission. Only enable for actions you fully trust.", + "description": "Auto-approve allows Roo Code to perform actions without asking for permission. Only enable for actions you fully trust. More detailed configuration available in Settings.", "actions": { "readFiles": { "label": "Read files and directories", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index ddb09122aa..fa054ff766 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "Auto-aprobar:", "none": "Ninguno", - "description": "Auto-aprobar permite a Roo Code realizar acciones sin pedir permiso. Habilita solo para acciones en las que confíes plenamente.", + "description": "Auto-aprobar permite a Roo Code realizar acciones sin pedir permiso. Habilita solo para acciones en las que confíes plenamente. Configuración más detallada disponible en Configuración.", "actions": { "readFiles": { "label": "Leer archivos y directorios", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 6577f275ce..85d013ced1 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "Auto-approbation :", "none": "Aucune", - "description": "L'auto-approbation permet à Roo Code d'effectuer des actions sans demander d'autorisation. Activez-la uniquement pour les actions auxquelles vous faites entièrement confiance.", + "description": "L'auto-approbation permet à Roo Code d'effectuer des actions sans demander d'autorisation. Activez-la uniquement pour les actions auxquelles vous faites entièrement confiance. Configuration plus détaillée disponible dans les Paramètres.", "actions": { "readFiles": { "label": "Lire fichiers et répertoires", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 2b85828b08..6faced6a27 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "स्वत:-स्वीकृति:", "none": "कोई नहीं", - "description": "स्वत:-स्वीकृति Roo Code को अनुमति मांगे बिना क्रियाएँ करने की अनुमति देती है। केवल उन क्रियाओं के लिए सक्षम करें जिन पर आप पूरी तरह से विश्वास करते हैं।", + "description": "स्वत:-स्वीकृति Roo Code को अनुमति मांगे बिना क्रियाएँ करने की अनुमति देती है। केवल उन क्रियाओं के लिए सक्षम करें जिन पर आप पूरी तरह से विश्वास करते हैं। अधिक विस्तृत कॉन्फ़िगरेशन सेटिंग्स में उपलब्ध है।", "actions": { "readFiles": { "label": "फ़ाइलें और निर्देशिकाएँ पढ़ें", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index d14ff9d051..b10c33e302 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "Auto-approvazione:", "none": "Nessuna", - "description": "L'auto-approvazione permette a Roo Code di eseguire azioni senza chiedere permesso. Abilita solo per azioni di cui ti fidi completamente.", + "description": "L'auto-approvazione permette a Roo Code di eseguire azioni senza chiedere permesso. Abilita solo per azioni di cui ti fidi completamente. Configurazione più dettagliata disponibile nelle Impostazioni.", "actions": { "readFiles": { "label": "Leggi file e directory", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 7f4fdd7e32..887bda5a05 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "自動承認:", "none": "なし", - "description": "自動承認はRoo Codeに許可を求めずに操作を実行する権限を与えます。完全に信頼できる操作のみ有効にしてください。", + "description": "自動承認はRoo Codeに許可を求めずに操作を実行する権限を与えます。完全に信頼できる操作のみ有効にしてください。より詳細な設定は設定で利用できます。", "actions": { "readFiles": { "label": "ファイルとディレクトリの読み取り", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 4aa5960b6a..633ff812d7 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "자동 승인:", "none": "없음", - "description": "자동 승인을 사용하면 Roo Code가 권한을 요청하지 않고 작업을 수행할 수 있습니다. 완전히 신뢰할 수 있는 작업에만 활성화하세요.", + "description": "자동 승인을 사용하면 Roo Code가 권한을 요청하지 않고 작업을 수행할 수 있습니다. 완전히 신뢰할 수 있는 작업에만 활성화하세요. 더 자세한 구성은 설정에서 사용할 수 있습니다.", "actions": { "readFiles": { "label": "파일 및 디렉토리 읽기", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 8157d91db0..6711df86c8 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "Automatyczne zatwierdzanie:", "none": "Brak", - "description": "Automatyczne zatwierdzanie pozwala Roo Code wykonywać działania bez pytania o pozwolenie. Włącz tylko dla działań, którym w pełni ufasz.", + "description": "Automatyczne zatwierdzanie pozwala Roo Code wykonywać działania bez pytania o pozwolenie. Włącz tylko dla działań, którym w pełni ufasz. Bardziej szczegółowa konfiguracja dostępna w Ustawieniach.", "actions": { "readFiles": { "label": "Czytaj pliki i katalogi", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 5c52c8bea9..ccb7ec734e 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "Aprovação automática:", "none": "Nenhuma", - "description": "A aprovação automática permite que o Roo Code execute ações sem pedir permissão. Ative apenas para ações nas quais você confia totalmente.", + "description": "A aprovação automática permite que o Roo Code execute ações sem pedir permissão. Ative apenas para ações nas quais você confia totalmente. Configuração mais detalhada disponível nas Configurações.", "actions": { "readFiles": { "label": "Ler arquivos e diretórios", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index d14b7a3ee2..1738bff644 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "Otomatik-onay:", "none": "Hiçbiri", - "description": "Otomatik onay, Roo Code'un izin istemeden işlemler gerçekleştirmesine olanak tanır. Yalnızca tamamen güvendiğiniz eylemler için etkinleştirin.", + "description": "Otomatik onay, Roo Code'un izin istemeden işlemler gerçekleştirmesine olanak tanır. Yalnızca tamamen güvendiğiniz eylemler için etkinleştirin. Daha detaylı yapılandırma Ayarlar'da mevcuttur.", "actions": { "readFiles": { "label": "Dosyaları ve dizinleri oku", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index eabc7bb746..97efe4bd57 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "Tự động phê duyệt:", "none": "Không", - "description": "Tự động phê duyệt cho phép Roo Code thực hiện hành động mà không cần xin phép. Chỉ bật cho các hành động bạn hoàn toàn tin tưởng.", + "description": "Tự động phê duyệt cho phép Roo Code thực hiện hành động mà không cần xin phép. Chỉ bật cho các hành động bạn hoàn toàn tin tưởng. Cấu hình chi tiết hơn có sẵn trong Cài đặt.", "actions": { "readFiles": { "label": "Đọc tệp và thư mục", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 7d6205dc70..eb86f94529 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "自动批准:", "none": "无", - "description": "自动批准允许Roo Code无需请求许可即可执行操作。仅为您完全信任的操作启用。", + "description": "自动批准允许Roo Code无需请求许可即可执行操作。仅为您完全信任的操作启用。更详细的配置可在设置中查看。", "actions": { "readFiles": { "label": "读取文件和目录", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index bfd69270e8..13c1e81745 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -159,7 +159,7 @@ "autoApprove": { "title": "自動批准:", "none": "無", - "description": "自動批准允許Roo Code無需請求許可即可執行操作。僅為您完全信任的操作啟用。", + "description": "自動批准允許Roo Code無需請求許可即可執行操作。僅為您完全信任的操作啟用。更詳細的配置可在設定中查看。", "actions": { "readFiles": { "label": "讀取檔案和目錄", From 592494fb2924ef8aaff4411ff19192e3473758a0 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 30 Mar 2025 13:12:18 -0400 Subject: [PATCH 31/38] Link to provider docs from the API options (#2112) --- .../src/components/settings/ApiOptions.tsx | 51 +++++++++++++++++-- .../src/components/settings/constants.ts | 1 + webview-ui/src/i18n/locales/ca/settings.json | 1 + webview-ui/src/i18n/locales/de/settings.json | 1 + webview-ui/src/i18n/locales/en/settings.json | 1 + webview-ui/src/i18n/locales/es/settings.json | 1 + webview-ui/src/i18n/locales/fr/settings.json | 1 + webview-ui/src/i18n/locales/hi/settings.json | 1 + webview-ui/src/i18n/locales/it/settings.json | 1 + webview-ui/src/i18n/locales/ja/settings.json | 1 + webview-ui/src/i18n/locales/ko/settings.json | 1 + webview-ui/src/i18n/locales/pl/settings.json | 1 + .../src/i18n/locales/pt-BR/settings.json | 1 + webview-ui/src/i18n/locales/tr/settings.json | 1 + webview-ui/src/i18n/locales/vi/settings.json | 1 + .../src/i18n/locales/zh-CN/settings.json | 1 + .../src/i18n/locales/zh-TW/settings.json | 1 + 17 files changed, 64 insertions(+), 3 deletions(-) diff --git a/webview-ui/src/components/settings/ApiOptions.tsx b/webview-ui/src/components/settings/ApiOptions.tsx index 3ed1158f54..cf6eec02d6 100644 --- a/webview-ui/src/components/settings/ApiOptions.tsx +++ b/webview-ui/src/components/settings/ApiOptions.tsx @@ -243,10 +243,55 @@ const ApiOptions = ({ [selectedProvider], ) + // Base URL for provider documentation + const DOC_BASE_URL = "https://docs.roocode.com/providers" + + // Custom URL path mappings for providers with different slugs + const providerUrlSlugs: Record = { + "openai-native": "openai", + openai: "openai-compatible", + } + + // Helper function to get provider display name from PROVIDERS constant + const getProviderDisplayName = (providerKey: string): string | undefined => { + const provider = PROVIDERS.find((p) => p.value === providerKey) + return provider?.label + } + + // Helper function to get the documentation URL and name for the currently selected provider + const getSelectedProviderDocUrl = (): { url: string; name: string } | undefined => { + const displayName = getProviderDisplayName(selectedProvider) + if (!displayName) { + return undefined + } + + // Get the URL slug - use custom mapping if available, otherwise use the provider key + const urlSlug = providerUrlSlugs[selectedProvider] || selectedProvider + + return { + url: `${DOC_BASE_URL}/${urlSlug}`, + name: displayName, + } + } + return (
-
- +
+
+ + {getSelectedProviderDocUrl() && ( +
+ + {t("settings:providers.providerDocumentation", { + provider: getSelectedProviderDocUrl()!.name, + })} + +
+ )} +