From 83fcbfed436b8178bdd3b00ab6a6a902ee9e5a05 Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Mon, 8 Sep 2025 14:22:40 +0100 Subject: [PATCH 01/20] Moves auto-approve to below the text box, removes global on/off setting --- .../components/chat/AutoApproveDropdown.tsx | 312 ++++++++++++++++++ .../src/components/chat/ChatTextArea.tsx | 4 + webview-ui/src/components/chat/ChatView.tsx | 25 -- webview-ui/src/i18n/locales/en/chat.json | 5 +- 4 files changed, 319 insertions(+), 27 deletions(-) create mode 100644 webview-ui/src/components/chat/AutoApproveDropdown.tsx diff --git a/webview-ui/src/components/chat/AutoApproveDropdown.tsx b/webview-ui/src/components/chat/AutoApproveDropdown.tsx new file mode 100644 index 000000000000..64366dbf547b --- /dev/null +++ b/webview-ui/src/components/chat/AutoApproveDropdown.tsx @@ -0,0 +1,312 @@ +import React from "react" +import { Stamp, ListChecks, LayoutList, Settings } from "lucide-react" + +import { vscode } from "@/utils/vscode" +import { cn } from "@/lib/utils" +import { useExtensionState } from "@/context/ExtensionStateContext" +import { useAppTranslation } from "@/i18n/TranslationContext" +import { useRooPortal } from "@/components/ui/hooks/useRooPortal" +import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui" +import { AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle" +import { useAutoApprovalToggles } from "@/hooks/useAutoApprovalToggles" +import { useAutoApprovalState } from "@/hooks/useAutoApprovalState" + +interface AutoApproveDropdownProps { + disabled?: boolean + title?: string + triggerClassName?: string +} + +export const AutoApproveDropdown = ({ disabled = false, title, triggerClassName = "" }: AutoApproveDropdownProps) => { + const [open, setOpen] = React.useState(false) + const portalContainer = useRooPortal("roo-portal") + const { t } = useAppTranslation() + + const { + autoApprovalEnabled, + setAutoApprovalEnabled, + alwaysApproveResubmit, + setAlwaysAllowReadOnly, + setAlwaysAllowWrite, + setAlwaysAllowExecute, + setAlwaysAllowBrowser, + setAlwaysAllowMcp, + setAlwaysAllowModeSwitch, + setAlwaysAllowSubtasks, + setAlwaysApproveResubmit, + setAlwaysAllowFollowupQuestions, + setAlwaysAllowUpdateTodoList, + } = useExtensionState() + + const baseToggles = useAutoApprovalToggles() + + // Include alwaysApproveResubmit in addition to the base toggles + const toggles = React.useMemo( + () => ({ + ...baseToggles, + alwaysApproveResubmit: alwaysApproveResubmit, + }), + [baseToggles, alwaysApproveResubmit], + ) + + const { hasEnabledOptions, effectiveAutoApprovalEnabled } = useAutoApprovalState(toggles, autoApprovalEnabled) + + const onAutoApproveToggle = React.useCallback( + (key: AutoApproveSetting, value: boolean) => { + vscode.postMessage({ type: key, bool: value }) + + // Update the specific toggle state + switch (key) { + case "alwaysAllowReadOnly": + setAlwaysAllowReadOnly(value) + break + case "alwaysAllowWrite": + setAlwaysAllowWrite(value) + break + case "alwaysAllowExecute": + setAlwaysAllowExecute(value) + break + case "alwaysAllowBrowser": + setAlwaysAllowBrowser(value) + break + case "alwaysAllowMcp": + setAlwaysAllowMcp(value) + break + case "alwaysAllowModeSwitch": + setAlwaysAllowModeSwitch(value) + break + case "alwaysAllowSubtasks": + setAlwaysAllowSubtasks(value) + break + case "alwaysApproveResubmit": + setAlwaysApproveResubmit(value) + break + case "alwaysAllowFollowupQuestions": + setAlwaysAllowFollowupQuestions(value) + break + case "alwaysAllowUpdateTodoList": + setAlwaysAllowUpdateTodoList(value) + break + } + + // If enabling any option, ensure autoApprovalEnabled is true + if (value && !autoApprovalEnabled) { + setAutoApprovalEnabled(true) + vscode.postMessage({ type: "autoApprovalEnabled", bool: true }) + } + }, + [ + autoApprovalEnabled, + setAlwaysAllowReadOnly, + setAlwaysAllowWrite, + setAlwaysAllowExecute, + setAlwaysAllowBrowser, + setAlwaysAllowMcp, + setAlwaysAllowModeSwitch, + setAlwaysAllowSubtasks, + setAlwaysApproveResubmit, + setAlwaysAllowFollowupQuestions, + setAlwaysAllowUpdateTodoList, + setAutoApprovalEnabled, + ], + ) + + const handleSelectAll = React.useCallback(() => { + // Enable all options + Object.keys(autoApproveSettingsConfig).forEach((key) => { + onAutoApproveToggle(key as AutoApproveSetting, true) + }) + // Enable master auto-approval + if (!autoApprovalEnabled) { + setAutoApprovalEnabled(true) + vscode.postMessage({ type: "autoApprovalEnabled", bool: true }) + } + }, [onAutoApproveToggle, autoApprovalEnabled, setAutoApprovalEnabled]) + + const handleSelectNone = React.useCallback(() => { + // Disable all options + Object.keys(autoApproveSettingsConfig).forEach((key) => { + onAutoApproveToggle(key as AutoApproveSetting, false) + }) + // Disable master auto-approval + if (autoApprovalEnabled) { + setAutoApprovalEnabled(false) + vscode.postMessage({ type: "autoApprovalEnabled", bool: false }) + } + }, [onAutoApproveToggle, autoApprovalEnabled, setAutoApprovalEnabled]) + + const handleOpenSettings = React.useCallback( + () => + window.postMessage({ type: "action", action: "settingsButtonClicked", values: { section: "autoApprove" } }), + [], + ) + + // Create display text for enabled actions + const displayText = React.useMemo(() => { + if (!effectiveAutoApprovalEnabled || !hasEnabledOptions) { + return t("chat:autoApprove.none") + } + const enabledActionsList = Object.entries(toggles) + .filter(([_key, value]) => !!value) + .map(([key]) => t(autoApproveSettingsConfig[key as AutoApproveSetting].labelKey)) + .join(", ") + + return enabledActionsList || t("chat:autoApprove.none") + }, [effectiveAutoApprovalEnabled, hasEnabledOptions, toggles, t]) + + // Split settings into two columns + const settingsArray = Object.values(autoApproveSettingsConfig) + const halfLength = Math.ceil(settingsArray.length / 2) + const firstColumn = settingsArray.slice(0, halfLength) + const secondColumn = settingsArray.slice(halfLength) + + return ( + + + + + {displayText} + + + +
+ {/* Header with description */} +
+
+

+ {t("chat:autoApprove.title")} +

+ +
+

+ {t("chat:autoApprove.description")} +

+
+ + {/* Two-column layout for approval options */} +
+
+ {/* First Column */} +
+ {firstColumn.map(({ key, labelKey, descriptionKey, icon }) => { + const isEnabled = toggles[key] + return ( + + + + ) + })} +
+ + {/* Second Column */} +
+ {secondColumn.map(({ key, labelKey, descriptionKey, icon }) => { + const isEnabled = toggles[key] + return ( + + + + ) + })} +
+
+
+ + {/* Bottom bar with Select All/None buttons */} +
+
+ + + + + + +
+
+
+
+
+ ) +} diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index c917797283cd..903c49e82742 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -26,6 +26,7 @@ import { StandardTooltip } from "@src/components/ui" import Thumbnails from "../common/Thumbnails" import { ModeSelector } from "./ModeSelector" import { ApiConfigSelector } from "./ApiConfigSelector" +import { AutoApproveDropdown } from "./AutoApproveDropdown" import { MAX_IMAGES_PER_MESSAGE } from "./ChatView" import ContextMenu from "./ContextMenu" import { IndexingStatusBadge } from "./IndexingStatusBadge" @@ -1199,6 +1200,9 @@ export const ChatTextArea = forwardRef( togglePinnedApiConfig={togglePinnedApiConfig} /> +
+ +
{isTtsPlaying && ( diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 3e13905bc9fa..4965e270eca2 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -51,7 +51,6 @@ import BrowserSessionRow from "./BrowserSessionRow" import ChatRow from "./ChatRow" import { ChatTextArea } from "./ChatTextArea" import TaskHeader from "./TaskHeader" -import AutoApproveMenu from "./AutoApproveMenu" import SystemPromptWarning from "./SystemPromptWarning" import ProfileViolationWarning from "./ProfileViolationWarning" import { CheckpointWarning } from "./CheckpointWarning" @@ -1839,27 +1838,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction )} - {/* - // Flex layout explanation: - // 1. Content div above uses flex: "1 1 0" to: - // - Grow to fill available space (flex-grow: 1) - // - Shrink when AutoApproveMenu needs space (flex-shrink: 1) - // - Start from zero size (flex-basis: 0) to ensure proper distribution - // minHeight: 0 allows it to shrink below its content height - // - // 2. AutoApproveMenu uses flex: "0 1 auto" to: - // - Not grow beyond its content (flex-grow: 0) - // - Shrink when viewport is small (flex-shrink: 1) - // - Use its content size as basis (flex-basis: auto) - // This ensures it takes its natural height when there's space - // but becomes scrollable when the viewport is too small - */} - {!task && ( -
- -
- )} - {task && ( <>
@@ -1881,9 +1859,6 @@ const ChatViewComponent: React.ForwardRefRenderFunction
-
- -
{areButtonsVisible && (
Settings.", + "description": "Run these actions without asking for permission. Only enable for actions you fully trust.", "selectOptionsFirst": "Select at least one option below to enable auto-approval", "toggleAriaLabel": "Toggle auto-approval", "disabledAriaLabel": "Auto-approval disabled - select options first" From b3992a2256f2e27b5987b7e7f82e3ca6e3f0a8fb Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:30:02 -0500 Subject: [PATCH 02/20] Revert PR #7188 - Restore temperature parameter to fix TabbyApi/ExLlamaV2 crashes (#7594) --- src/api/providers/__tests__/chutes.spec.ts | 5 ++ src/api/providers/__tests__/fireworks.spec.ts | 1 + src/api/providers/__tests__/groq.spec.ts | 79 +------------------ src/api/providers/__tests__/openai.spec.ts | 67 +--------------- src/api/providers/__tests__/roo.spec.ts | 6 +- src/api/providers/__tests__/sambanova.spec.ts | 1 + src/api/providers/__tests__/zai.spec.ts | 1 + .../base-openai-compatible-provider.ts | 8 +- src/api/providers/openai.ts | 9 +-- 9 files changed, 17 insertions(+), 160 deletions(-) diff --git a/src/api/providers/__tests__/chutes.spec.ts b/src/api/providers/__tests__/chutes.spec.ts index f6fd1ef04567..398f86ce6083 100644 --- a/src/api/providers/__tests__/chutes.spec.ts +++ b/src/api/providers/__tests__/chutes.spec.ts @@ -409,6 +409,10 @@ describe("ChutesHandler", () => { content: `${systemPrompt}\n${messages[0].content}`, }, ], + max_tokens: 32768, + temperature: 0.6, + stream: true, + stream_options: { include_usage: true }, }), ) }) @@ -438,6 +442,7 @@ describe("ChutesHandler", () => { expect.objectContaining({ model: modelId, max_tokens: modelInfo.maxTokens, + temperature: 0.5, messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), stream: true, stream_options: { include_usage: true }, diff --git a/src/api/providers/__tests__/fireworks.spec.ts b/src/api/providers/__tests__/fireworks.spec.ts index ed1e119a99f1..f07c1797a05f 100644 --- a/src/api/providers/__tests__/fireworks.spec.ts +++ b/src/api/providers/__tests__/fireworks.spec.ts @@ -373,6 +373,7 @@ describe("FireworksHandler", () => { expect.objectContaining({ model: modelId, max_tokens: modelInfo.maxTokens, + temperature: 0.5, messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), stream: true, stream_options: { include_usage: true }, diff --git a/src/api/providers/__tests__/groq.spec.ts b/src/api/providers/__tests__/groq.spec.ts index 52846617f43b..66bf0690a8fe 100644 --- a/src/api/providers/__tests__/groq.spec.ts +++ b/src/api/providers/__tests__/groq.spec.ts @@ -160,11 +160,7 @@ describe("GroqHandler", () => { it("createMessage should pass correct parameters to Groq client", async () => { const modelId: GroqModelId = "llama-3.1-8b-instant" const modelInfo = groqModels[modelId] - const handlerWithModel = new GroqHandler({ - apiModelId: modelId, - groqApiKey: "test-groq-api-key", - modelTemperature: 0.5, // Explicitly set temperature for this test - }) + const handlerWithModel = new GroqHandler({ apiModelId: modelId, groqApiKey: "test-groq-api-key" }) mockCreate.mockImplementationOnce(() => { return { @@ -194,77 +190,4 @@ describe("GroqHandler", () => { undefined, ) }) - - it("should omit temperature when modelTemperature is undefined", async () => { - const modelId: GroqModelId = "llama-3.1-8b-instant" - const handlerWithoutTemp = new GroqHandler({ - apiModelId: modelId, - groqApiKey: "test-groq-api-key", - // modelTemperature is not set - }) - - mockCreate.mockImplementationOnce(() => { - return { - [Symbol.asyncIterator]: () => ({ - async next() { - return { done: true } - }, - }), - } - }) - - const systemPrompt = "Test system prompt" - const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message" }] - - const messageGenerator = handlerWithoutTemp.createMessage(systemPrompt, messages) - await messageGenerator.next() - - expect(mockCreate).toHaveBeenCalledWith( - expect.objectContaining({ - model: modelId, - messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), - stream: true, - }), - undefined, - ) - - // Verify temperature is NOT included - const callArgs = mockCreate.mock.calls[0][0] - expect(callArgs).not.toHaveProperty("temperature") - }) - - it("should include temperature when modelTemperature is explicitly set", async () => { - const modelId: GroqModelId = "llama-3.1-8b-instant" - const handlerWithTemp = new GroqHandler({ - apiModelId: modelId, - groqApiKey: "test-groq-api-key", - modelTemperature: 0.7, - }) - - mockCreate.mockImplementationOnce(() => { - return { - [Symbol.asyncIterator]: () => ({ - async next() { - return { done: true } - }, - }), - } - }) - - const systemPrompt = "Test system prompt" - const messages: Anthropic.Messages.MessageParam[] = [{ role: "user", content: "Test message" }] - - const messageGenerator = handlerWithTemp.createMessage(systemPrompt, messages) - await messageGenerator.next() - - expect(mockCreate).toHaveBeenCalledWith( - expect.objectContaining({ - model: modelId, - temperature: 0.7, - messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), - stream: true, - }), - undefined, - ) - }) }) diff --git a/src/api/providers/__tests__/openai.spec.ts b/src/api/providers/__tests__/openai.spec.ts index 14ed35430a54..3e744d6e16ea 100644 --- a/src/api/providers/__tests__/openai.spec.ts +++ b/src/api/providers/__tests__/openai.spec.ts @@ -315,71 +315,6 @@ describe("OpenAiHandler", () => { const callArgs = mockCreate.mock.calls[0][0] expect(callArgs.max_completion_tokens).toBe(4096) }) - - it("should omit temperature when modelTemperature is undefined", async () => { - const optionsWithoutTemperature: ApiHandlerOptions = { - ...mockOptions, - // modelTemperature is not set, should not include temperature - } - const handlerWithoutTemperature = new OpenAiHandler(optionsWithoutTemperature) - const stream = handlerWithoutTemperature.createMessage(systemPrompt, messages) - // Consume the stream to trigger the API call - for await (const _chunk of stream) { - } - // Assert the mockCreate was called without temperature - expect(mockCreate).toHaveBeenCalled() - const callArgs = mockCreate.mock.calls[0][0] - expect(callArgs).not.toHaveProperty("temperature") - }) - - it("should include temperature when modelTemperature is explicitly set to 0", async () => { - const optionsWithZeroTemperature: ApiHandlerOptions = { - ...mockOptions, - modelTemperature: 0, - } - const handlerWithZeroTemperature = new OpenAiHandler(optionsWithZeroTemperature) - const stream = handlerWithZeroTemperature.createMessage(systemPrompt, messages) - // Consume the stream to trigger the API call - for await (const _chunk of stream) { - } - // Assert the mockCreate was called with temperature: 0 - expect(mockCreate).toHaveBeenCalled() - const callArgs = mockCreate.mock.calls[0][0] - expect(callArgs.temperature).toBe(0) - }) - - it("should include temperature when modelTemperature is set to a non-zero value", async () => { - const optionsWithCustomTemperature: ApiHandlerOptions = { - ...mockOptions, - modelTemperature: 0.7, - } - const handlerWithCustomTemperature = new OpenAiHandler(optionsWithCustomTemperature) - const stream = handlerWithCustomTemperature.createMessage(systemPrompt, messages) - // Consume the stream to trigger the API call - for await (const _chunk of stream) { - } - // Assert the mockCreate was called with temperature: 0.7 - expect(mockCreate).toHaveBeenCalled() - const callArgs = mockCreate.mock.calls[0][0] - expect(callArgs.temperature).toBe(0.7) - }) - - it("should include DEEP_SEEK_DEFAULT_TEMPERATURE for deepseek-reasoner models when temperature is not set", async () => { - const deepseekOptions: ApiHandlerOptions = { - ...mockOptions, - openAiModelId: "deepseek-reasoner", - // modelTemperature is not set - } - const deepseekHandler = new OpenAiHandler(deepseekOptions) - const stream = deepseekHandler.createMessage(systemPrompt, messages) - // Consume the stream to trigger the API call - for await (const _chunk of stream) { - } - // Assert the mockCreate was called with DEEP_SEEK_DEFAULT_TEMPERATURE (0.6) - expect(mockCreate).toHaveBeenCalled() - const callArgs = mockCreate.mock.calls[0][0] - expect(callArgs.temperature).toBe(0.6) - }) }) describe("error handling", () => { @@ -515,7 +450,7 @@ describe("OpenAiHandler", () => { ], stream: true, stream_options: { include_usage: true }, - // temperature should be omitted when not set + temperature: 0, }, { path: "/models/chat/completions" }, ) diff --git a/src/api/providers/__tests__/roo.spec.ts b/src/api/providers/__tests__/roo.spec.ts index 5509c51e0284..5897156b0a08 100644 --- a/src/api/providers/__tests__/roo.spec.ts +++ b/src/api/providers/__tests__/roo.spec.ts @@ -354,7 +354,7 @@ describe("RooHandler", () => { }) describe("temperature and model configuration", () => { - it("should omit temperature when not explicitly set", async () => { + it("should use default temperature of 0.7", async () => { handler = new RooHandler(mockOptions) const stream = handler.createMessage(systemPrompt, messages) for await (const _chunk of stream) { @@ -362,8 +362,8 @@ describe("RooHandler", () => { } expect(mockCreate).toHaveBeenCalledWith( - expect.not.objectContaining({ - temperature: expect.anything(), + expect.objectContaining({ + temperature: 0.7, }), undefined, ) diff --git a/src/api/providers/__tests__/sambanova.spec.ts b/src/api/providers/__tests__/sambanova.spec.ts index 81de3058c89c..d8cae8bf8067 100644 --- a/src/api/providers/__tests__/sambanova.spec.ts +++ b/src/api/providers/__tests__/sambanova.spec.ts @@ -144,6 +144,7 @@ describe("SambaNovaHandler", () => { expect.objectContaining({ model: modelId, max_tokens: modelInfo.maxTokens, + temperature: 0.7, messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), stream: true, stream_options: { include_usage: true }, diff --git a/src/api/providers/__tests__/zai.spec.ts b/src/api/providers/__tests__/zai.spec.ts index 6882cfe448be..a16aa9fcdfed 100644 --- a/src/api/providers/__tests__/zai.spec.ts +++ b/src/api/providers/__tests__/zai.spec.ts @@ -220,6 +220,7 @@ describe("ZAiHandler", () => { expect.objectContaining({ model: modelId, max_tokens: modelInfo.maxTokens, + temperature: ZAI_DEFAULT_TEMPERATURE, messages: expect.arrayContaining([{ role: "system", content: systemPrompt }]), stream: true, stream_options: { include_usage: true }, diff --git a/src/api/providers/base-openai-compatible-provider.ts b/src/api/providers/base-openai-compatible-provider.ts index 5d2b9425e7cf..fb6c5d03770e 100644 --- a/src/api/providers/base-openai-compatible-provider.ts +++ b/src/api/providers/base-openai-compatible-provider.ts @@ -74,19 +74,17 @@ export abstract class BaseOpenAiCompatibleProvider info: { maxTokens: max_tokens }, } = this.getModel() + const temperature = this.options.modelTemperature ?? this.defaultTemperature + const params: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model, max_tokens, + temperature, messages: [{ role: "system", content: systemPrompt }, ...convertToOpenAiMessages(messages)], stream: true, stream_options: { include_usage: true }, } - // Only include temperature if explicitly set - if (this.options.modelTemperature !== undefined) { - params.temperature = this.options.modelTemperature - } - try { return this.client.chat.completions.create(params, requestOptions) } catch (error) { diff --git a/src/api/providers/openai.ts b/src/api/providers/openai.ts index 2a57f2513185..aebe671712a7 100644 --- a/src/api/providers/openai.ts +++ b/src/api/providers/openai.ts @@ -159,20 +159,13 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl const requestOptions: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming = { model: modelId, + temperature: this.options.modelTemperature ?? (deepseekReasoner ? DEEP_SEEK_DEFAULT_TEMPERATURE : 0), messages: convertedMessages, stream: true as const, ...(isGrokXAI ? {} : { stream_options: { include_usage: true } }), ...(reasoning && reasoning), } - // Only include temperature if explicitly set - if (this.options.modelTemperature !== undefined) { - requestOptions.temperature = this.options.modelTemperature - } else if (deepseekReasoner) { - // DeepSeek Reasoner has a specific default temperature - requestOptions.temperature = DEEP_SEEK_DEFAULT_TEMPERATURE - } - // Add max_tokens if needed this.addMaxTokensIfNeeded(requestOptions, modelInfo) From d777e9af442fa668c6eb28f2b51ffe9e148dd836 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Mon, 8 Sep 2025 21:46:59 -0500 Subject: [PATCH 03/20] fix: reduce CodeBlock button z-index to prevent overlap with popovers (#7783) Fixes #7703 - CodeBlock language dropdown and copy button were appearing above popovers due to z-index: 100. Reduced to z-index: 40 to maintain proper layering hierarchy while keeping buttons functional. --- webview-ui/src/components/common/CodeBlock.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/components/common/CodeBlock.tsx b/webview-ui/src/components/common/CodeBlock.tsx index 28492acd8b8e..ef415e342c49 100644 --- a/webview-ui/src/components/common/CodeBlock.tsx +++ b/webview-ui/src/components/common/CodeBlock.tsx @@ -74,7 +74,7 @@ const CodeBlockButtonWrapper = styled.div` top: var(--copy-button-top); right: var(--copy-button-right, 8px); height: auto; - z-index: 100; + z-index: 40; background: ${CODE_BLOCK_BG_COLOR}${WRAPPER_ALPHA}; overflow: visible; pointer-events: none; From 562c9161159d32de8112a53ef740e164497e1ec5 Mon Sep 17 00:00:00 2001 From: ItsOnlyBinary Date: Tue, 9 Sep 2025 03:48:22 +0100 Subject: [PATCH 04/20] Make ollama models info transport work like lmstudio (#7679) --- .../__tests__/webviewMessageHandler.spec.ts | 42 +++++++++++++++++++ src/core/webview/webviewMessageHandler.ts | 4 +- src/shared/ExtensionMessage.ts | 2 +- .../components/settings/providers/Ollama.tsx | 15 ++++--- .../components/ui/hooks/useOllamaModels.ts | 39 +++++++++++++++++ .../components/ui/hooks/useSelectedModel.ts | 9 +++- 6 files changed, 99 insertions(+), 12 deletions(-) create mode 100644 webview-ui/src/components/ui/hooks/useOllamaModels.ts diff --git a/src/core/webview/__tests__/webviewMessageHandler.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.spec.ts index 429dc87856c1..12d76151248f 100644 --- a/src/core/webview/__tests__/webviewMessageHandler.spec.ts +++ b/src/core/webview/__tests__/webviewMessageHandler.spec.ts @@ -136,6 +136,48 @@ describe("webviewMessageHandler - requestLmStudioModels", () => { }) }) +describe("webviewMessageHandler - requestOllamaModels", () => { + beforeEach(() => { + vi.clearAllMocks() + mockClineProvider.getState = vi.fn().mockResolvedValue({ + apiConfiguration: { + ollamaModelId: "model-1", + ollamaBaseUrl: "http://localhost:1234", + }, + }) + }) + + it("successfully fetches models from Ollama", async () => { + const mockModels: ModelRecord = { + "model-1": { + maxTokens: 4096, + contextWindow: 8192, + supportsPromptCache: false, + description: "Test model 1", + }, + "model-2": { + maxTokens: 8192, + contextWindow: 16384, + supportsPromptCache: false, + description: "Test model 2", + }, + } + + mockGetModels.mockResolvedValue(mockModels) + + await webviewMessageHandler(mockClineProvider, { + type: "requestOllamaModels", + }) + + expect(mockGetModels).toHaveBeenCalledWith({ provider: "ollama", baseUrl: "http://localhost:1234" }) + + expect(mockClineProvider.postMessageToWebview).toHaveBeenCalledWith({ + type: "ollamaModels", + ollamaModels: mockModels, + }) + }) +}) + describe("webviewMessageHandler - requestRouterModels", () => { beforeEach(() => { vi.clearAllMocks() diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 080fbbcd9437..6ad1864064e4 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -797,7 +797,7 @@ export const webviewMessageHandler = async ( if (routerName === "ollama" && Object.keys(result.value.models).length > 0) { provider.postMessageToWebview({ type: "ollamaModels", - ollamaModels: Object.keys(result.value.models), + ollamaModels: result.value.models, }) } else if (routerName === "lmstudio" && Object.keys(result.value.models).length > 0) { provider.postMessageToWebview({ @@ -842,7 +842,7 @@ export const webviewMessageHandler = async ( if (Object.keys(ollamaModels).length > 0) { provider.postMessageToWebview({ type: "ollamaModels", - ollamaModels: Object.keys(ollamaModels), + ollamaModels: ollamaModels, }) } } catch (error) { diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index d08c66e36bcd..1565bb8c5252 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -148,7 +148,7 @@ export interface ExtensionMessage { clineMessage?: ClineMessage routerModels?: RouterModels openAiModels?: string[] - ollamaModels?: string[] + ollamaModels?: ModelRecord lmStudioModels?: ModelRecord vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[] huggingFaceModels?: Array<{ diff --git a/webview-ui/src/components/settings/providers/Ollama.tsx b/webview-ui/src/components/settings/providers/Ollama.tsx index b09ecad5d623..b3ff00ccdda9 100644 --- a/webview-ui/src/components/settings/providers/Ollama.tsx +++ b/webview-ui/src/components/settings/providers/Ollama.tsx @@ -11,6 +11,7 @@ import { useRouterModels } from "@src/components/ui/hooks/useRouterModels" import { vscode } from "@src/utils/vscode" import { inputEventTransform } from "../transforms" +import { ModelRecord } from "@roo/api" type OllamaProps = { apiConfiguration: ProviderSettings @@ -20,7 +21,7 @@ type OllamaProps = { export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaProps) => { const { t } = useAppTranslation() - const [ollamaModels, setOllamaModels] = useState([]) + const [ollamaModels, setOllamaModels] = useState({}) const routerModels = useRouterModels() const handleInputChange = useCallback( @@ -40,7 +41,7 @@ export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaPro switch (message.type) { case "ollamaModels": { - const newModels = message.ollamaModels ?? [] + const newModels = message.ollamaModels ?? {} setOllamaModels(newModels) } break @@ -61,7 +62,7 @@ export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaPro if (!selectedModel) return false // Check if model exists in local ollama models - if (ollamaModels.length > 0 && ollamaModels.includes(selectedModel)) { + if (Object.keys(ollamaModels).length > 0 && selectedModel in ollamaModels) { return false // Model is available locally } @@ -116,15 +117,13 @@ export const Ollama = ({ apiConfiguration, setApiConfigurationField }: OllamaPro
)} - {ollamaModels.length > 0 && ( + {Object.keys(ollamaModels).length > 0 && ( - {ollamaModels.map((model) => ( + {Object.keys(ollamaModels).map((model) => ( {model} diff --git a/webview-ui/src/components/ui/hooks/useOllamaModels.ts b/webview-ui/src/components/ui/hooks/useOllamaModels.ts new file mode 100644 index 000000000000..67a172b0d834 --- /dev/null +++ b/webview-ui/src/components/ui/hooks/useOllamaModels.ts @@ -0,0 +1,39 @@ +import { useQuery } from "@tanstack/react-query" + +import { ModelRecord } from "@roo/api" +import { ExtensionMessage } from "@roo/ExtensionMessage" + +import { vscode } from "@src/utils/vscode" + +const getOllamaModels = async () => + new Promise((resolve, reject) => { + const cleanup = () => { + window.removeEventListener("message", handler) + } + + const timeout = setTimeout(() => { + cleanup() + reject(new Error("Ollama models request timed out")) + }, 10000) + + const handler = (event: MessageEvent) => { + const message: ExtensionMessage = event.data + + if (message.type === "ollamaModels") { + clearTimeout(timeout) + cleanup() + + if (message.ollamaModels) { + resolve(message.ollamaModels) + } else { + reject(new Error("No Ollama models in response")) + } + } + } + + window.addEventListener("message", handler) + vscode.postMessage({ type: "requestOllamaModels" }) + }) + +export const useOllamaModels = (modelId?: string) => + useQuery({ queryKey: ["ollamaModels"], queryFn: () => (modelId ? getOllamaModels() : {}) }) diff --git a/webview-ui/src/components/ui/hooks/useSelectedModel.ts b/webview-ui/src/components/ui/hooks/useSelectedModel.ts index b7fe4ff03d95..f8a005e86a12 100644 --- a/webview-ui/src/components/ui/hooks/useSelectedModel.ts +++ b/webview-ui/src/components/ui/hooks/useSelectedModel.ts @@ -64,19 +64,23 @@ import type { ModelRecord, RouterModels } from "@roo/api" import { useRouterModels } from "./useRouterModels" import { useOpenRouterModelProviders } from "./useOpenRouterModelProviders" import { useLmStudioModels } from "./useLmStudioModels" +import { useOllamaModels } from "./useOllamaModels" export const useSelectedModel = (apiConfiguration?: ProviderSettings) => { const provider = apiConfiguration?.apiProvider || "anthropic" const openRouterModelId = provider === "openrouter" ? apiConfiguration?.openRouterModelId : undefined const lmStudioModelId = provider === "lmstudio" ? apiConfiguration?.lmStudioModelId : undefined + const ollamaModelId = provider === "ollama" ? apiConfiguration?.ollamaModelId : undefined const routerModels = useRouterModels() const openRouterModelProviders = useOpenRouterModelProviders(openRouterModelId) const lmStudioModels = useLmStudioModels(lmStudioModelId) + const ollamaModels = useOllamaModels(ollamaModelId) const { id, info } = apiConfiguration && (typeof lmStudioModelId === "undefined" || typeof lmStudioModels.data !== "undefined") && + (typeof ollamaModelId === "undefined" || typeof ollamaModels.data !== "undefined") && typeof routerModels.data !== "undefined" && typeof openRouterModelProviders.data !== "undefined" ? getSelectedModel({ @@ -85,6 +89,7 @@ export const useSelectedModel = (apiConfiguration?: ProviderSettings) => { routerModels: routerModels.data, openRouterModelProviders: openRouterModelProviders.data, lmStudioModels: lmStudioModels.data, + ollamaModels: ollamaModels.data, }) : { id: anthropicDefaultModelId, info: undefined } @@ -109,12 +114,14 @@ function getSelectedModel({ routerModels, openRouterModelProviders, lmStudioModels, + ollamaModels, }: { provider: ProviderName apiConfiguration: ProviderSettings routerModels: RouterModels openRouterModelProviders: Record lmStudioModels: ModelRecord | undefined + ollamaModels: ModelRecord | undefined }): { id: string; info: ModelInfo | undefined } { // the `undefined` case are used to show the invalid selection to prevent // users from seeing the default model if their selection is invalid @@ -255,7 +262,7 @@ function getSelectedModel({ } case "ollama": { const id = apiConfiguration.ollamaModelId ?? "" - const info = routerModels.ollama && routerModels.ollama[id] + const info = ollamaModels && ollamaModels[apiConfiguration.ollamaModelId!] return { id, info: info || undefined, From 66fb77bd1cf0d431a6a9b62bb8605ce930258b0c Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 22:50:21 -0400 Subject: [PATCH 05/20] feat: add click-to-edit, ESC-to-cancel, and fix padding consistency for chat messages (#7790) * feat: add click-to-edit, ESC-to-cancel, and fix padding consistency - Enable click-to-edit for past messages by making message text clickable - Add ESC key handler to cancel edit mode in ChatTextArea - Fix padding consistency between past and queued message editors - Adjust right padding for edit mode to accommodate cancel button Fixes #7788 * fix: adjust padding and layout for ChatTextArea in edit mode * refactor: replace hardcoded pr-[72px] with standard Tailwind pr-20 class --------- Co-authored-by: Roo Code Co-authored-by: Hannes Rudolph Co-authored-by: daniel-lxs --- webview-ui/src/components/chat/ChatRow.tsx | 15 ++++-- .../src/components/chat/ChatTextArea.tsx | 48 +++++++++---------- 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 7b3107a2bed0..f24e2ca6602f 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -1172,9 +1172,10 @@ export const ChatRowContent = ({ ) case "user_feedback": return ( -
+
{isEditing ? ( -
+
) : (
-
+
{ + e.stopPropagation() + if (!isStreaming) { + handleEditClick() + } + }} + title={t("chat:queuedMessages.clickToEdit")}>
diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 903c49e82742..a4441e51629f 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -904,20 +904,8 @@ export const ChatTextArea = forwardRef( return (
( : isDraggingOver ? "border-2 border-dashed border-vscode-focusBorder" : "border border-transparent", - "px-[8px]", - "py-1.5", - "pr-9", + "pl-2", + "py-2", + isEditMode ? "pr-20" : "pr-9", "z-10", "forced-color-adjust-none", + "rounded", )} style={{ color: "transparent", @@ -1031,7 +1020,15 @@ export const ChatTextArea = forwardRef( updateHighlights() }} onFocus={() => setIsFocused(true)} - onKeyDown={handleKeyDown} + onKeyDown={(e) => { + // Handle ESC to cancel in edit mode + if (isEditMode && e.key === "Escape" && !e.nativeEvent?.isComposing) { + e.preventDefault() + onCancel?.() + return + } + handleKeyDown(e) + }} onKeyUp={handleKeyUp} onBlur={handleBlur} onPaste={handlePaste} @@ -1055,7 +1052,7 @@ export const ChatTextArea = forwardRef( "text-vscode-editor-font-size", "leading-vscode-editor-line-height", "cursor-text", - "py-1.5 px-2", + "py-2 pl-2", isFocused ? "border border-vscode-focusBorder outline outline-vscode-focusBorder" : isDraggingOver @@ -1072,7 +1069,7 @@ export const ChatTextArea = forwardRef( "resize-none", "overflow-x-hidden", "overflow-y-auto", - "pr-9", + isEditMode ? "pr-20" : "pr-9", "flex-none flex-grow", "z-[2]", "scrollbar-none", @@ -1081,7 +1078,7 @@ export const ChatTextArea = forwardRef( onScroll={() => updateHighlights()} /> -
+
-
+
{isEditMode && ( + )} +
+ )} + + {showManualEntry && ( + // Manual URL entry form +
+

+ {t("cloud:pasteCallbackUrl")} +

+ + +
+ )}
)} diff --git a/webview-ui/src/i18n/locales/ca/cloud.json b/webview-ui/src/i18n/locales/ca/cloud.json index 02714c7c4b54..94c12d75e146 100644 --- a/webview-ui/src/i18n/locales/ca/cloud.json +++ b/webview-ui/src/i18n/locales/ca/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Permet seguir i interactuar amb tasques en aquest espai de treball amb Roo Code Cloud", "visitCloudWebsite": "Visita Roo Code Cloud", - "cloudUrlPillLabel": "URL de Roo Code Cloud" + "cloudUrlPillLabel": "URL de Roo Code Cloud", + "authWaiting": "Esperant que es completi l'autenticació...", + "havingTrouble": "Tens problemes?", + "pasteCallbackUrl": "Copia l'URL de redirect del teu navegador i enganxa-la aquí:", + "startOver": "Torna a començar" } diff --git a/webview-ui/src/i18n/locales/de/cloud.json b/webview-ui/src/i18n/locales/de/cloud.json index cbba34539998..e76245f43047 100644 --- a/webview-ui/src/i18n/locales/de/cloud.json +++ b/webview-ui/src/i18n/locales/de/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Ermöglicht das Verfolgen und Interagieren mit Aufgaben in diesem Arbeitsbereich mit Roo Code Cloud", "visitCloudWebsite": "Roo Code Cloud besuchen", - "cloudUrlPillLabel": "Roo Code Cloud URL" + "cloudUrlPillLabel": "Roo Code Cloud URL", + "authWaiting": "Warte auf Abschluss der Authentifizierung...", + "havingTrouble": "Probleme?", + "pasteCallbackUrl": "Kopiere die Redirect-URL aus deinem Browser und füge sie hier ein:", + "startOver": "Von vorne beginnen" } diff --git a/webview-ui/src/i18n/locales/en/cloud.json b/webview-ui/src/i18n/locales/en/cloud.json index 88948c91532b..c436f50bc445 100644 --- a/webview-ui/src/i18n/locales/en/cloud.json +++ b/webview-ui/src/i18n/locales/en/cloud.json @@ -13,5 +13,9 @@ "visitCloudWebsite": "Visit Roo Code Cloud", "remoteControl": "Roomote Control", "remoteControlDescription": "Enable following and interacting with tasks in this workspace with Roo Code Cloud", - "cloudUrlPillLabel": "Roo Code Cloud URL" + "cloudUrlPillLabel": "Roo Code Cloud URL", + "authWaiting": "Waiting for browser authentication...", + "havingTrouble": "Having trouble?", + "pasteCallbackUrl": "Copy the redirect URL from your browser and paste it here:", + "startOver": "Start over" } diff --git a/webview-ui/src/i18n/locales/es/cloud.json b/webview-ui/src/i18n/locales/es/cloud.json index 2497edf7bf2b..1515a2a91731 100644 --- a/webview-ui/src/i18n/locales/es/cloud.json +++ b/webview-ui/src/i18n/locales/es/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Permite seguir e interactuar con tareas en este espacio de trabajo con Roo Code Cloud", "visitCloudWebsite": "Visitar Roo Code Cloud", - "cloudUrlPillLabel": "URL de Roo Code Cloud" + "cloudUrlPillLabel": "URL de Roo Code Cloud", + "authWaiting": "Esperando que se complete la autenticación...", + "havingTrouble": "¿Tienes problemas?", + "pasteCallbackUrl": "Copia la URL de redirect desde tu navegador y pégala aquí:", + "startOver": "Empezar de nuevo" } diff --git a/webview-ui/src/i18n/locales/fr/cloud.json b/webview-ui/src/i18n/locales/fr/cloud.json index 76db922933ef..5ed35af6a7e9 100644 --- a/webview-ui/src/i18n/locales/fr/cloud.json +++ b/webview-ui/src/i18n/locales/fr/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Permet de suivre et d'interagir avec les tâches dans cet espace de travail avec Roo Code Cloud", "visitCloudWebsite": "Visiter Roo Code Cloud", - "cloudUrlPillLabel": "URL de Roo Code Cloud" + "cloudUrlPillLabel": "URL de Roo Code Cloud", + "authWaiting": "En attente de la fin de l'authentification...", + "havingTrouble": "Des difficultés ?", + "pasteCallbackUrl": "Copie l'URL de redirect depuis ton navigateur et colle-la ici :", + "startOver": "Recommencer" } diff --git a/webview-ui/src/i18n/locales/hi/cloud.json b/webview-ui/src/i18n/locales/hi/cloud.json index 60d7103c2571..c10e7de35cce 100644 --- a/webview-ui/src/i18n/locales/hi/cloud.json +++ b/webview-ui/src/i18n/locales/hi/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Roo Code Cloud के साथ इस वर्कस्पेस में कार्यों को फॉलो और इंटरैक्ट करने की सुविधा दें", "visitCloudWebsite": "Roo Code Cloud पर जाएं", - "cloudUrlPillLabel": "Roo Code Cloud URL" + "cloudUrlPillLabel": "Roo Code Cloud URL", + "authWaiting": "प्रमाणीकरण पूरा होने की प्रतीक्षा कर रहे हैं...", + "havingTrouble": "समस्या हो रही है?", + "pasteCallbackUrl": "अपने ब्राउज़र से redirect URL कॉपी करें और यहाँ पेस्ट करें:", + "startOver": "फिर से शुरू करें" } diff --git a/webview-ui/src/i18n/locales/id/cloud.json b/webview-ui/src/i18n/locales/id/cloud.json index e48bb16fe8ad..27ea5c4bb785 100644 --- a/webview-ui/src/i18n/locales/id/cloud.json +++ b/webview-ui/src/i18n/locales/id/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Memungkinkan mengikuti dan berinteraksi dengan tugas di workspace ini dengan Roo Code Cloud", "visitCloudWebsite": "Kunjungi Roo Code Cloud", - "cloudUrlPillLabel": "URL Roo Code Cloud" + "cloudUrlPillLabel": "URL Roo Code Cloud", + "authWaiting": "Menunggu autentikasi selesai...", + "havingTrouble": "Ada masalah?", + "pasteCallbackUrl": "Salin URL redirect dari browser dan tempel di sini:", + "startOver": "Mulai dari awal" } diff --git a/webview-ui/src/i18n/locales/it/cloud.json b/webview-ui/src/i18n/locales/it/cloud.json index 0678fcd7211c..74bd07144cdd 100644 --- a/webview-ui/src/i18n/locales/it/cloud.json +++ b/webview-ui/src/i18n/locales/it/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Abilita il monitoraggio e l'interazione con le attività in questo workspace con Roo Code Cloud", "visitCloudWebsite": "Visita Roo Code Cloud", - "cloudUrlPillLabel": "URL di Roo Code Cloud" + "cloudUrlPillLabel": "URL di Roo Code Cloud", + "authWaiting": "In attesa del completamento dell'autenticazione...", + "havingTrouble": "Hai problemi?", + "pasteCallbackUrl": "Copia l'URL di redirect dal tuo browser e incollalo qui:", + "startOver": "Ricomincia" } diff --git a/webview-ui/src/i18n/locales/ja/cloud.json b/webview-ui/src/i18n/locales/ja/cloud.json index 4b409af9e060..1ab89d043584 100644 --- a/webview-ui/src/i18n/locales/ja/cloud.json +++ b/webview-ui/src/i18n/locales/ja/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Roo Code Cloudでこのワークスペースのタスクをフォローし操作することを有効にする", "visitCloudWebsite": "Roo Code Cloudを訪問", - "cloudUrlPillLabel": "Roo Code Cloud URL" + "cloudUrlPillLabel": "Roo Code Cloud URL", + "authWaiting": "認証完了をお待ちください...", + "havingTrouble": "問題が発生していますか?", + "pasteCallbackUrl": "ブラウザからリダイレクトURLをコピーし、ここに貼り付けてください:", + "startOver": "最初からやり直す" } diff --git a/webview-ui/src/i18n/locales/ko/cloud.json b/webview-ui/src/i18n/locales/ko/cloud.json index 4272a94acf80..96b4760ee316 100644 --- a/webview-ui/src/i18n/locales/ko/cloud.json +++ b/webview-ui/src/i18n/locales/ko/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Roo Code Cloud로 이 워크스페이스의 작업을 팔로우하고 상호작용할 수 있게 합니다", "visitCloudWebsite": "Roo Code Cloud 방문", - "cloudUrlPillLabel": "Roo Code Cloud URL" + "cloudUrlPillLabel": "Roo Code Cloud URL", + "authWaiting": "인증 완료를 기다리는 중...", + "havingTrouble": "문제가 있나요?", + "pasteCallbackUrl": "브라우저에서 리다이렉트 URL을 복사하여 여기에 붙여넣으세요:", + "startOver": "다시 시작" } diff --git a/webview-ui/src/i18n/locales/nl/cloud.json b/webview-ui/src/i18n/locales/nl/cloud.json index f77a37fbf086..5c2651d44aa9 100644 --- a/webview-ui/src/i18n/locales/nl/cloud.json +++ b/webview-ui/src/i18n/locales/nl/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Schakel het volgen en interacteren met taken in deze workspace in met Roo Code Cloud", "visitCloudWebsite": "Bezoek Roo Code Cloud", - "cloudUrlPillLabel": "Roo Code Cloud URL" + "cloudUrlPillLabel": "Roo Code Cloud URL", + "authWaiting": "Wachten tot authenticatie voltooid is...", + "havingTrouble": "Problemen?", + "pasteCallbackUrl": "Kopieer de redirect-URL uit je browser en plak hem hier:", + "startOver": "Opnieuw beginnen" } diff --git a/webview-ui/src/i18n/locales/pl/cloud.json b/webview-ui/src/i18n/locales/pl/cloud.json index 4f98bf0b9867..0cb860ee1f29 100644 --- a/webview-ui/src/i18n/locales/pl/cloud.json +++ b/webview-ui/src/i18n/locales/pl/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Umożliwia śledzenie i interakcję z zadaniami w tym obszarze roboczym za pomocą Roo Code Cloud", "visitCloudWebsite": "Odwiedź Roo Code Cloud", - "cloudUrlPillLabel": "URL Roo Code Cloud" + "cloudUrlPillLabel": "URL Roo Code Cloud", + "authWaiting": "Oczekiwanie na zakończenie uwierzytelniania...", + "havingTrouble": "Masz problemy?", + "pasteCallbackUrl": "Skopiuj URL redirect z przeglądarki i wklej tutaj:", + "startOver": "Zacznij od nowa" } diff --git a/webview-ui/src/i18n/locales/pt-BR/cloud.json b/webview-ui/src/i18n/locales/pt-BR/cloud.json index 749395edae64..7e0a29e38b46 100644 --- a/webview-ui/src/i18n/locales/pt-BR/cloud.json +++ b/webview-ui/src/i18n/locales/pt-BR/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Permite acompanhar e interagir com tarefas neste workspace com Roo Code Cloud", "visitCloudWebsite": "Visitar Roo Code Cloud", - "cloudUrlPillLabel": "URL do Roo Code Cloud " + "cloudUrlPillLabel": "URL do Roo Code Cloud ", + "authWaiting": "Aguardando conclusão da autenticação...", + "havingTrouble": "Tendo problemas?", + "pasteCallbackUrl": "Copie a URL de redirect do seu navegador e cole aqui:", + "startOver": "Recomeçar" } diff --git a/webview-ui/src/i18n/locales/ru/cloud.json b/webview-ui/src/i18n/locales/ru/cloud.json index 5fd6dc372b42..b30d6064a23e 100644 --- a/webview-ui/src/i18n/locales/ru/cloud.json +++ b/webview-ui/src/i18n/locales/ru/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Позволяет отслеживать и взаимодействовать с задачами в этом рабочем пространстве с Roo Code Cloud", "visitCloudWebsite": "Посетить Roo Code Cloud", - "cloudUrlPillLabel": "URL Roo Code Cloud" + "cloudUrlPillLabel": "URL Roo Code Cloud", + "authWaiting": "Ожидание завершения аутентификации...", + "havingTrouble": "Проблемы?", + "pasteCallbackUrl": "Скопируй URL перенаправления из браузера и вставь его сюда:", + "startOver": "Начать заново" } diff --git a/webview-ui/src/i18n/locales/tr/cloud.json b/webview-ui/src/i18n/locales/tr/cloud.json index 822e837a9fb9..c82661163f23 100644 --- a/webview-ui/src/i18n/locales/tr/cloud.json +++ b/webview-ui/src/i18n/locales/tr/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Bu çalışma alanındaki görevleri Roo Code Cloud ile takip etme ve etkileşim kurma imkanı sağlar", "visitCloudWebsite": "Roo Code Cloud'u ziyaret et", - "cloudUrlPillLabel": "Roo Code Cloud URL'si" + "cloudUrlPillLabel": "Roo Code Cloud URL'si", + "authWaiting": "Kimlik doğrulama tamamlanması bekleniyor...", + "havingTrouble": "Sorun yaşıyor musun?", + "pasteCallbackUrl": "Tarayıcından redirect URL'sini kopyala ve buraya yapıştır:", + "startOver": "Baştan başla" } diff --git a/webview-ui/src/i18n/locales/vi/cloud.json b/webview-ui/src/i18n/locales/vi/cloud.json index ef444e70bd91..ee5c2683b0c2 100644 --- a/webview-ui/src/i18n/locales/vi/cloud.json +++ b/webview-ui/src/i18n/locales/vi/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "Cho phép theo dõi và tương tác với các tác vụ trong workspace này với Roo Code Cloud", "visitCloudWebsite": "Truy cập Roo Code Cloud", - "cloudUrlPillLabel": "URL Roo Code Cloud" + "cloudUrlPillLabel": "URL Roo Code Cloud", + "authWaiting": "Đang chờ hoàn tất xác thực...", + "havingTrouble": "Gặp vấn đề?", + "pasteCallbackUrl": "Sao chép URL redirect từ trình duyệt và dán vào đây:", + "startOver": "Bắt đầu lại" } diff --git a/webview-ui/src/i18n/locales/zh-CN/cloud.json b/webview-ui/src/i18n/locales/zh-CN/cloud.json index 5a90cb8ccd92..c42647a0967d 100644 --- a/webview-ui/src/i18n/locales/zh-CN/cloud.json +++ b/webview-ui/src/i18n/locales/zh-CN/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "允许通过 Roo Code Cloud 跟踪和操作此工作区中的任务", "visitCloudWebsite": "访问 Roo Code Cloud", - "cloudUrlPillLabel": "Roo Code Cloud URL" + "cloudUrlPillLabel": "Roo Code Cloud URL", + "authWaiting": "等待身份验证完成...", + "havingTrouble": "遇到问题?", + "pasteCallbackUrl": "从浏览器复制重定向 URL 并粘贴到这里:", + "startOver": "重新开始" } diff --git a/webview-ui/src/i18n/locales/zh-TW/cloud.json b/webview-ui/src/i18n/locales/zh-TW/cloud.json index 034c15e2049b..efb2b25ad997 100644 --- a/webview-ui/src/i18n/locales/zh-TW/cloud.json +++ b/webview-ui/src/i18n/locales/zh-TW/cloud.json @@ -14,5 +14,9 @@ "remoteControl": "Roomote Control", "remoteControlDescription": "允許透過 Roo Code Cloud 追蹤和操作此工作區中的工作", "visitCloudWebsite": "造訪 Roo Code Cloud", - "cloudUrlPillLabel": "Roo Code Cloud URL" + "cloudUrlPillLabel": "Roo Code Cloud URL", + "authWaiting": "等待身份驗證完成...", + "havingTrouble": "遇到問題?", + "pasteCallbackUrl": "從瀏覽器複製重新導向 URL 並貼上到這裡:", + "startOver": "重新開始" } From 98668bafe513c0af9a1746d1ee7c0112deb01fc8 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:26:44 -0500 Subject: [PATCH 07/20] fix: resolve chat message edit/delete duplication issues (#7793) --- .../webview/__tests__/ClineProvider.spec.ts | 56 +-- .../webviewMessageHandler.delete.spec.ts | 245 +++++++++++ .../webviewMessageHandler.edit.spec.ts | 390 ++++++++++++++++++ src/core/webview/webviewMessageHandler.ts | 135 ++++-- src/i18n/locales/ca/common.json | 9 + src/i18n/locales/de/common.json | 9 + src/i18n/locales/en/common.json | 9 + src/i18n/locales/es/common.json | 9 + src/i18n/locales/fr/common.json | 9 + src/i18n/locales/hi/common.json | 9 + src/i18n/locales/id/common.json | 9 + src/i18n/locales/it/common.json | 9 + src/i18n/locales/ja/common.json | 9 + src/i18n/locales/ko/common.json | 9 + src/i18n/locales/nl/common.json | 9 + src/i18n/locales/pl/common.json | 9 + src/i18n/locales/pt-BR/common.json | 9 + src/i18n/locales/ru/common.json | 9 + src/i18n/locales/tr/common.json | 9 + src/i18n/locales/vi/common.json | 9 + src/i18n/locales/zh-CN/common.json | 9 + src/i18n/locales/zh-TW/common.json | 9 + 22 files changed, 917 insertions(+), 71 deletions(-) create mode 100644 src/core/webview/__tests__/webviewMessageHandler.delete.spec.ts create mode 100644 src/core/webview/__tests__/webviewMessageHandler.edit.spec.ts diff --git a/src/core/webview/__tests__/ClineProvider.spec.ts b/src/core/webview/__tests__/ClineProvider.spec.ts index 375de1cd8911..cd41ce09ce9e 100644 --- a/src/core/webview/__tests__/ClineProvider.spec.ts +++ b/src/core/webview/__tests__/ClineProvider.spec.ts @@ -1332,19 +1332,11 @@ describe("ClineProvider", () => { text: "Edited message content", }) - // Verify correct messages were kept (only messages before the edited one) - expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([ - mockMessages[0], - mockMessages[1], - mockMessages[2], - ]) + // Verify correct messages were kept - delete from the preceding user message to truly replace it + expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([]) - // Verify correct API messages were kept (only messages before the edited one) - expect(mockCline.overwriteApiConversationHistory).toHaveBeenCalledWith([ - mockApiHistory[0], - mockApiHistory[1], - mockApiHistory[2], - ]) + // Verify correct API messages were kept + expect(mockCline.overwriteApiConversationHistory).toHaveBeenCalledWith([]) // The new flow calls webviewMessageHandler recursively with askResponse // We need to verify the recursive call happened by checking if the handler was called again @@ -3016,7 +3008,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { mockCline.apiConversationHistory = [{ ts: 1000 }, { ts: 2000 }, { ts: 3000 }] as any[] mockCline.overwriteClineMessages = vi.fn() mockCline.overwriteApiConversationHistory = vi.fn() - mockCline.handleWebviewAskResponse = vi.fn() + mockCline.submitUserMessage = vi.fn() await provider.addClineToStack(mockCline) ;(provider as any).getTaskWithId = vi.fn().mockResolvedValue({ @@ -3046,9 +3038,11 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { text: "Edited message with preserved images", }) - // Verify messages were edited correctly - messages up to the edited message should remain - expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([mockMessages[0], mockMessages[1]]) - expect(mockCline.overwriteApiConversationHistory).toHaveBeenCalledWith([{ ts: 1000 }, { ts: 2000 }]) + // Verify messages were edited correctly - the ORIGINAL user message and all subsequent messages are removed + expect(mockCline.overwriteClineMessages).toHaveBeenCalledWith([mockMessages[0]]) + expect(mockCline.overwriteApiConversationHistory).toHaveBeenCalledWith([{ ts: 1000 }]) + // Verify submitUserMessage was called with the edited content + expect(mockCline.submitUserMessage).toHaveBeenCalledWith("Edited message with preserved images", undefined) }) test("handles editing messages with file attachments", async () => { @@ -3070,7 +3064,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { mockCline.apiConversationHistory = [{ ts: 1000 }, { ts: 2000 }, { ts: 3000 }] as any[] mockCline.overwriteClineMessages = vi.fn() mockCline.overwriteApiConversationHistory = vi.fn() - mockCline.handleWebviewAskResponse = vi.fn() + mockCline.submitUserMessage = vi.fn() await provider.addClineToStack(mockCline) ;(provider as any).getTaskWithId = vi.fn().mockResolvedValue({ @@ -3101,11 +3095,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { }) expect(mockCline.overwriteClineMessages).toHaveBeenCalled() - expect(mockCline.handleWebviewAskResponse).toHaveBeenCalledWith( - "messageResponse", - "Edited message with file attachment", - undefined, - ) + expect(mockCline.submitUserMessage).toHaveBeenCalledWith("Edited message with file attachment", undefined) }) }) @@ -3197,7 +3187,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { await messageHandler({ type: "editMessageConfirm", messageTs: 2000, text: "Edited message" }) // The error should be caught and shown - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Error editing message: Connection lost") + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("errors.message.error_editing_message") }) }) @@ -3320,7 +3310,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { text: "Edited message", }) - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Error editing message: Unauthorized") + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("errors.message.error_editing_message") }) describe("Malformed Requests and Invalid Formats", () => { @@ -3544,7 +3534,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { // Verify cleanup was attempted before failure expect(cleanupSpy).toHaveBeenCalled() - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Error editing message: Operation failed") + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("errors.message.error_editing_message") }) test("validates proper cleanup during failed delete operations", async () => { @@ -3584,9 +3574,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { // Verify cleanup was attempted before failure expect(cleanupSpy).toHaveBeenCalled() - expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( - "Error deleting message: Delete operation failed", - ) + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("errors.message.error_deleting_message") }) }) @@ -3609,7 +3597,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { mockCline.apiConversationHistory = [{ ts: 1000 }, { ts: 2000 }] as any[] mockCline.overwriteClineMessages = vi.fn() mockCline.overwriteApiConversationHistory = vi.fn() - mockCline.handleWebviewAskResponse = vi.fn() + mockCline.submitUserMessage = vi.fn() await provider.addClineToStack(mockCline) ;(provider as any).getTaskWithId = vi.fn().mockResolvedValue({ @@ -3638,11 +3626,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { await messageHandler({ type: "editMessageConfirm", messageTs: 2000, text: largeEditedContent }) expect(mockCline.overwriteClineMessages).toHaveBeenCalled() - expect(mockCline.handleWebviewAskResponse).toHaveBeenCalledWith( - "messageResponse", - largeEditedContent, - undefined, - ) + expect(mockCline.submitUserMessage).toHaveBeenCalledWith(largeEditedContent, undefined) }) test("handles deleting messages with large payloads", async () => { @@ -3822,7 +3806,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { ] as any[] mockCline.overwriteClineMessages = vi.fn() mockCline.overwriteApiConversationHistory = vi.fn() - mockCline.handleWebviewAskResponse = vi.fn() + mockCline.submitUserMessage = vi.fn() await provider.addClineToStack(mockCline) ;(provider as any).getTaskWithId = vi.fn().mockResolvedValue({ @@ -3855,7 +3839,7 @@ describe("ClineProvider - Comprehensive Edit/Delete Edge Cases", () => { // Should handle future timestamps correctly expect(mockCline.overwriteClineMessages).toHaveBeenCalled() - expect(mockCline.handleWebviewAskResponse).toHaveBeenCalled() + expect(mockCline.submitUserMessage).toHaveBeenCalled() }) }) }) diff --git a/src/core/webview/__tests__/webviewMessageHandler.delete.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.delete.spec.ts new file mode 100644 index 000000000000..28f6ba9cf885 --- /dev/null +++ b/src/core/webview/__tests__/webviewMessageHandler.delete.spec.ts @@ -0,0 +1,245 @@ +import { describe, it, expect, beforeEach, vi } from "vitest" +import { webviewMessageHandler } from "../webviewMessageHandler" +import * as vscode from "vscode" +import { ClineProvider } from "../ClineProvider" + +// Mock the saveTaskMessages function +vi.mock("../../task-persistence", () => ({ + saveTaskMessages: vi.fn(), +})) + +// Mock the i18n module +vi.mock("../../../i18n", () => ({ + t: vi.fn((key: string) => key), + changeLanguage: vi.fn(), +})) + +vi.mock("vscode", () => ({ + window: { + showErrorMessage: vi.fn(), + showWarningMessage: vi.fn(), + showInformationMessage: vi.fn(), + }, + workspace: { + workspaceFolders: undefined, + getConfiguration: vi.fn(() => ({ + get: vi.fn(), + update: vi.fn(), + })), + }, + ConfigurationTarget: { + Global: 1, + Workspace: 2, + WorkspaceFolder: 3, + }, + Uri: { + parse: vi.fn((str) => ({ toString: () => str })), + file: vi.fn((path) => ({ fsPath: path })), + }, + env: { + openExternal: vi.fn(), + clipboard: { + writeText: vi.fn(), + }, + }, + commands: { + executeCommand: vi.fn(), + }, +})) + +describe("webviewMessageHandler delete functionality", () => { + let provider: any + let getCurrentTaskMock: any + + beforeEach(() => { + // Reset all mocks + vi.clearAllMocks() + + // Create mock task + getCurrentTaskMock = { + clineMessages: [], + apiConversationHistory: [], + overwriteClineMessages: vi.fn(async () => {}), + overwriteApiConversationHistory: vi.fn(async () => {}), + taskId: "test-task-id", + } + + // Create mock provider + provider = { + getCurrentTask: vi.fn(() => getCurrentTaskMock), + postMessageToWebview: vi.fn(), + contextProxy: { + getValue: vi.fn(), + setValue: vi.fn(async () => {}), + globalStorageUri: { fsPath: "/test/path" }, + }, + log: vi.fn(), + cwd: "/test/cwd", + } + }) + + describe("handleDeleteMessageConfirm", () => { + it("should handle deletion when apiConversationHistoryIndex is -1 (message not in API history)", async () => { + // Setup test data with a user message and assistant response + const userMessageTs = 1000 + const assistantMessageTs = 1001 + + getCurrentTaskMock.clineMessages = [ + { ts: userMessageTs, say: "user", text: "Hello" }, + { ts: assistantMessageTs, say: "assistant", text: "Hi there" }, + ] + + // API history has the assistant message but not the user message + // This simulates the case where the user message wasn't in API history + getCurrentTaskMock.apiConversationHistory = [ + { ts: assistantMessageTs, role: "assistant", content: { type: "text", text: "Hi there" } }, + { + ts: 1002, + role: "assistant", + content: { type: "text", text: "attempt_completion" }, + name: "attempt_completion", + }, + ] + + // Call delete for the user message + await webviewMessageHandler(provider, { + type: "deleteMessageConfirm", + messageTs: userMessageTs, + }) + + // Verify that clineMessages was truncated at the correct index + expect(getCurrentTaskMock.overwriteClineMessages).toHaveBeenCalledWith([]) + + // When message is not found in API history (index is -1), + // API history should be truncated from the first API message at/after the deleted timestamp (fallback) + expect(getCurrentTaskMock.overwriteApiConversationHistory).toHaveBeenCalledWith([]) + }) + + it("should handle deletion when exact apiConversationHistoryIndex is found", async () => { + // Setup test data where message exists in both arrays + const messageTs = 1000 + + getCurrentTaskMock.clineMessages = [ + { ts: 900, say: "user", text: "Previous message" }, + { ts: messageTs, say: "user", text: "Delete this" }, + { ts: 1100, say: "assistant", text: "Response" }, + ] + + getCurrentTaskMock.apiConversationHistory = [ + { ts: 900, role: "user", content: { type: "text", text: "Previous message" } }, + { ts: messageTs, role: "user", content: { type: "text", text: "Delete this" } }, + { ts: 1100, role: "assistant", content: { type: "text", text: "Response" } }, + ] + + // Call delete + await webviewMessageHandler(provider, { + type: "deleteMessageConfirm", + messageTs: messageTs, + }) + + // Verify truncation at correct indices + expect(getCurrentTaskMock.overwriteClineMessages).toHaveBeenCalledWith([ + { ts: 900, say: "user", text: "Previous message" }, + ]) + + expect(getCurrentTaskMock.overwriteApiConversationHistory).toHaveBeenCalledWith([ + { ts: 900, role: "user", content: { type: "text", text: "Previous message" } }, + ]) + }) + + it("should handle deletion when message not found in clineMessages", async () => { + getCurrentTaskMock.clineMessages = [{ ts: 1000, say: "user", text: "Some message" }] + + getCurrentTaskMock.apiConversationHistory = [] + + // Call delete with non-existent timestamp + await webviewMessageHandler(provider, { + type: "deleteMessageConfirm", + messageTs: 9999, + }) + + // Verify error message was shown (expecting translation key since t() is mocked to return the key) + expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("common:errors.message.message_not_found") + + // Verify no truncation occurred + expect(getCurrentTaskMock.overwriteClineMessages).not.toHaveBeenCalled() + expect(getCurrentTaskMock.overwriteApiConversationHistory).not.toHaveBeenCalled() + }) + + it("should handle deletion with attempt_completion in API history", async () => { + // Setup test data with attempt_completion + const userMessageTs = 1000 + const attemptCompletionTs = 1001 + + getCurrentTaskMock.clineMessages = [ + { ts: userMessageTs, say: "user", text: "Fix the bug" }, + { ts: attemptCompletionTs, say: "assistant", text: "I've fixed the bug" }, + ] + + // API history has attempt_completion but user message is missing + getCurrentTaskMock.apiConversationHistory = [ + { + ts: attemptCompletionTs, + role: "assistant", + content: { + type: "text", + text: "I've fixed the bug in the code", + }, + name: "attempt_completion", + }, + { + ts: 1002, + role: "user", + content: { type: "text", text: "Looks good, but..." }, + }, + ] + + // Call delete for the user message + await webviewMessageHandler(provider, { + type: "deleteMessageConfirm", + messageTs: userMessageTs, + }) + + // Verify that clineMessages was truncated + expect(getCurrentTaskMock.overwriteClineMessages).toHaveBeenCalledWith([]) + + // API history should be truncated from first message at/after deleted timestamp (fallback) + expect(getCurrentTaskMock.overwriteApiConversationHistory).toHaveBeenCalledWith([]) + }) + + it("should preserve messages before the deleted one", async () => { + const messageTs = 2000 + + getCurrentTaskMock.clineMessages = [ + { ts: 1000, say: "user", text: "First message" }, + { ts: 1500, say: "assistant", text: "First response" }, + { ts: messageTs, say: "user", text: "Delete this" }, + { ts: 2500, say: "assistant", text: "Response to delete" }, + ] + + getCurrentTaskMock.apiConversationHistory = [ + { ts: 1000, role: "user", content: { type: "text", text: "First message" } }, + { ts: 1500, role: "assistant", content: { type: "text", text: "First response" } }, + { ts: messageTs, role: "user", content: { type: "text", text: "Delete this" } }, + { ts: 2500, role: "assistant", content: { type: "text", text: "Response to delete" } }, + ] + + await webviewMessageHandler(provider, { + type: "deleteMessageConfirm", + messageTs: messageTs, + }) + + // Should preserve messages before the deleted one + expect(getCurrentTaskMock.overwriteClineMessages).toHaveBeenCalledWith([ + { ts: 1000, say: "user", text: "First message" }, + { ts: 1500, say: "assistant", text: "First response" }, + ]) + + // API history should be truncated at the exact index + expect(getCurrentTaskMock.overwriteApiConversationHistory).toHaveBeenCalledWith([ + { ts: 1000, role: "user", content: { type: "text", text: "First message" } }, + { ts: 1500, role: "assistant", content: { type: "text", text: "First response" } }, + ]) + }) + }) +}) diff --git a/src/core/webview/__tests__/webviewMessageHandler.edit.spec.ts b/src/core/webview/__tests__/webviewMessageHandler.edit.spec.ts new file mode 100644 index 000000000000..d467f5cd92d9 --- /dev/null +++ b/src/core/webview/__tests__/webviewMessageHandler.edit.spec.ts @@ -0,0 +1,390 @@ +import type { Mock } from "vitest" +import { describe, it, expect, vi, beforeEach } from "vitest" + +// Mock dependencies first +vi.mock("vscode", () => ({ + window: { + showWarningMessage: vi.fn(), + showErrorMessage: vi.fn(), + }, + workspace: { + workspaceFolders: [{ uri: { fsPath: "/mock/workspace" } }], + getConfiguration: vi.fn().mockReturnValue({ + get: vi.fn(), + update: vi.fn(), + }), + }, + Uri: { + file: vi.fn((path) => ({ fsPath: path })), + }, + env: { + uriScheme: "vscode", + }, +})) + +vi.mock("../../task-persistence", () => ({ + saveTaskMessages: vi.fn(), +})) + +vi.mock("../../../api/providers/fetchers/modelCache", () => ({ + getModels: vi.fn(), + flushModels: vi.fn(), +})) + +vi.mock("../checkpointRestoreHandler", () => ({ + handleCheckpointRestoreOperation: vi.fn(), +})) + +// Import after mocks +import { webviewMessageHandler } from "../webviewMessageHandler" +import type { ClineProvider } from "../ClineProvider" +import type { ClineMessage } from "@roo-code/types" +import type { ApiMessage } from "../../task-persistence/apiMessages" + +describe("webviewMessageHandler - Edit Message with Timestamp Fallback", () => { + let mockClineProvider: ClineProvider + let mockCurrentTask: any + + beforeEach(() => { + vi.clearAllMocks() + + // Create a mock task with messages + mockCurrentTask = { + taskId: "test-task-id", + clineMessages: [] as ClineMessage[], + apiConversationHistory: [] as ApiMessage[], + overwriteClineMessages: vi.fn(), + overwriteApiConversationHistory: vi.fn(), + handleWebviewAskResponse: vi.fn(), + } + + // Create mock provider + mockClineProvider = { + getCurrentTask: vi.fn().mockReturnValue(mockCurrentTask), + postMessageToWebview: vi.fn(), + contextProxy: { + getValue: vi.fn(), + setValue: vi.fn(), + globalStorageUri: { fsPath: "/mock/storage" }, + }, + log: vi.fn(), + } as unknown as ClineProvider + }) + + it("should not modify API history when apiConversationHistoryIndex is -1", async () => { + // Setup: User message followed by attempt_completion + const userMessageTs = 1000 + const assistantMessageTs = 2000 + const completionMessageTs = 3000 + + // UI messages (clineMessages) + mockCurrentTask.clineMessages = [ + { + ts: userMessageTs, + type: "say", + say: "user_feedback", + text: "Hello", + } as ClineMessage, + { + ts: completionMessageTs, + type: "say", + say: "completion_result", + text: "Task Completed!", + } as ClineMessage, + ] + + // API conversation history - note the user message is missing (common scenario after condense) + mockCurrentTask.apiConversationHistory = [ + { + ts: assistantMessageTs, + role: "assistant", + content: [ + { + type: "text", + text: "I'll help you with that.", + }, + ], + }, + { + ts: completionMessageTs, + role: "assistant", + content: [ + { + type: "tool_use", + name: "attempt_completion", + id: "tool-1", + input: { + result: "Task Completed!", + }, + }, + ], + }, + ] as ApiMessage[] + + // Trigger edit confirmation + await webviewMessageHandler(mockClineProvider, { + type: "editMessageConfirm", + messageTs: userMessageTs, + text: "Hello World", // edited content + restoreCheckpoint: false, + }) + + // Verify that UI messages were truncated at the correct index + expect(mockCurrentTask.overwriteClineMessages).toHaveBeenCalledWith( + [], // All messages before index 0 (empty array) + ) + + // API history should be truncated from first message at/after edited timestamp (fallback) + expect(mockCurrentTask.overwriteApiConversationHistory).toHaveBeenCalledWith([]) + }) + + it("should preserve messages before the edited message when message not in API history", async () => { + const earlierMessageTs = 500 + const userMessageTs = 1000 + const assistantMessageTs = 2000 + + // UI messages + mockCurrentTask.clineMessages = [ + { + ts: earlierMessageTs, + type: "say", + say: "user_feedback", + text: "Earlier message", + } as ClineMessage, + { + ts: userMessageTs, + type: "say", + say: "user_feedback", + text: "Hello", + } as ClineMessage, + { + ts: assistantMessageTs, + type: "say", + say: "text", + text: "Response", + } as ClineMessage, + ] + + // API history - missing the exact user message at ts=1000 + mockCurrentTask.apiConversationHistory = [ + { + ts: earlierMessageTs, + role: "user", + content: [{ type: "text", text: "Earlier message" }], + }, + { + ts: assistantMessageTs, + role: "assistant", + content: [{ type: "text", text: "Response" }], + }, + ] as ApiMessage[] + + await webviewMessageHandler(mockClineProvider, { + type: "editMessageConfirm", + messageTs: userMessageTs, + text: "Hello World", + restoreCheckpoint: false, + }) + + // Verify UI messages were truncated to preserve earlier message + expect(mockCurrentTask.overwriteClineMessages).toHaveBeenCalledWith([ + { + ts: earlierMessageTs, + type: "say", + say: "user_feedback", + text: "Earlier message", + }, + ]) + + // API history should be truncated from the first API message at/after the edited timestamp (fallback) + expect(mockCurrentTask.overwriteApiConversationHistory).toHaveBeenCalledWith([ + { + ts: earlierMessageTs, + role: "user", + content: [{ type: "text", text: "Earlier message" }], + }, + ]) + }) + + it("should not use fallback when exact apiConversationHistoryIndex is found", async () => { + const userMessageTs = 1000 + const assistantMessageTs = 2000 + + // Both UI and API have the message at the same timestamp + mockCurrentTask.clineMessages = [ + { + ts: userMessageTs, + type: "say", + say: "user_feedback", + text: "Hello", + } as ClineMessage, + { + ts: assistantMessageTs, + type: "say", + say: "text", + text: "Response", + } as ClineMessage, + ] + + mockCurrentTask.apiConversationHistory = [ + { + ts: userMessageTs, + role: "user", + content: [{ type: "text", text: "Hello" }], + }, + { + ts: assistantMessageTs, + role: "assistant", + content: [{ type: "text", text: "Response" }], + }, + ] as ApiMessage[] + + await webviewMessageHandler(mockClineProvider, { + type: "editMessageConfirm", + messageTs: userMessageTs, + text: "Hello World", + restoreCheckpoint: false, + }) + + // Both should be truncated at index 0 + expect(mockCurrentTask.overwriteClineMessages).toHaveBeenCalledWith([]) + expect(mockCurrentTask.overwriteApiConversationHistory).toHaveBeenCalledWith([]) + }) + + it("should handle case where no API messages match timestamp criteria", async () => { + const userMessageTs = 3000 + + mockCurrentTask.clineMessages = [ + { + ts: userMessageTs, + type: "say", + say: "user_feedback", + text: "Hello", + } as ClineMessage, + ] + + // All API messages have timestamps before the edited message + mockCurrentTask.apiConversationHistory = [ + { + ts: 1000, + role: "assistant", + content: [{ type: "text", text: "Old message 1" }], + }, + { + ts: 2000, + role: "assistant", + content: [{ type: "text", text: "Old message 2" }], + }, + ] as ApiMessage[] + + await webviewMessageHandler(mockClineProvider, { + type: "editMessageConfirm", + messageTs: userMessageTs, + text: "Hello World", + restoreCheckpoint: false, + }) + + // UI messages truncated + expect(mockCurrentTask.overwriteClineMessages).toHaveBeenCalledWith([]) + + // API history should not be modified when no API messages meet the timestamp criteria + expect(mockCurrentTask.overwriteApiConversationHistory).not.toHaveBeenCalled() + }) + + it("should handle empty API conversation history gracefully", async () => { + const userMessageTs = 1000 + + mockCurrentTask.clineMessages = [ + { + ts: userMessageTs, + type: "say", + say: "user_feedback", + text: "Hello", + } as ClineMessage, + ] + + mockCurrentTask.apiConversationHistory = [] + + await webviewMessageHandler(mockClineProvider, { + type: "editMessageConfirm", + messageTs: userMessageTs, + text: "Hello World", + restoreCheckpoint: false, + }) + + // UI messages should be truncated + expect(mockCurrentTask.overwriteClineMessages).toHaveBeenCalledWith([]) + + // API history should not be modified when message not found + expect(mockCurrentTask.overwriteApiConversationHistory).not.toHaveBeenCalled() + }) + + it("should correctly handle attempt_completion in API history", async () => { + const userMessageTs = 1000 + const completionTs = 2000 + const feedbackTs = 3000 + + mockCurrentTask.clineMessages = [ + { + ts: userMessageTs, + type: "say", + say: "user_feedback", + text: "Do something", + } as ClineMessage, + { + ts: completionTs, + type: "say", + say: "completion_result", + text: "Task Completed!", + } as ClineMessage, + { + ts: feedbackTs, + type: "say", + say: "user_feedback", + text: "Thanks", + } as ClineMessage, + ] + + // API history with attempt_completion tool use (user message missing) + mockCurrentTask.apiConversationHistory = [ + { + ts: completionTs, + role: "assistant", + content: [ + { + type: "tool_use", + name: "attempt_completion", + id: "tool-1", + input: { + result: "Task Completed!", + }, + }, + ], + }, + { + ts: feedbackTs, + role: "user", + content: [ + { + type: "text", + text: "Thanks", + }, + ], + }, + ] as ApiMessage[] + + // Edit the first user message + await webviewMessageHandler(mockClineProvider, { + type: "editMessageConfirm", + messageTs: userMessageTs, + text: "Do something else", + restoreCheckpoint: false, + }) + + // UI messages truncated at edited message + expect(mockCurrentTask.overwriteClineMessages).toHaveBeenCalledWith([]) + + // API history should be truncated from first message at/after edited timestamp (fallback) + expect(mockCurrentTask.overwriteApiConversationHistory).toHaveBeenCalledWith([]) + }) +}) diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 8b872599bce8..d88d10d22a6e 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -84,6 +84,17 @@ export const webviewMessageHandler = async ( return { messageIndex, apiConversationHistoryIndex } } + /** + * Fallback: find first API history index at or after a timestamp. + * Used when the exact user message isn't present in apiConversationHistory (e.g., after condense). + */ + const findFirstApiIndexAtOrAfter = (ts: number, currentCline: any) => { + if (typeof ts !== "number") return -1 + return currentCline.apiConversationHistory.findIndex( + (msg: ApiMessage) => typeof msg?.ts === "number" && (msg.ts as number) >= ts, + ) + } + /** * Removes the target message and all subsequent messages */ @@ -109,18 +120,20 @@ export const webviewMessageHandler = async ( // Check if there's a checkpoint before this message const currentCline = provider.getCurrentTask() let hasCheckpoint = false - if (currentCline) { - const { messageIndex } = findMessageIndices(messageTs, currentCline) - if (messageIndex !== -1) { - // Find the last checkpoint before this message - const checkpoints = currentCline.clineMessages.filter( - (msg) => msg.say === "checkpoint_saved" && msg.ts > messageTs, - ) - hasCheckpoint = checkpoints.length > 0 - } else { - console.log("[webviewMessageHandler] Message not found! Looking for ts:", messageTs) - } + if (!currentCline) { + await vscode.window.showErrorMessage(t("common:errors.message.no_active_task_to_delete")) + return + } + + const { messageIndex } = findMessageIndices(messageTs, currentCline) + + if (messageIndex !== -1) { + // Find the last checkpoint before this message + const checkpoints = currentCline.clineMessages.filter( + (msg) => msg.say === "checkpoint_saved" && msg.ts > messageTs, + ) + hasCheckpoint = checkpoints.length > 0 } // Send message to webview to show delete confirmation dialog @@ -142,11 +155,15 @@ export const webviewMessageHandler = async ( } const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline) + // Determine API truncation index with timestamp fallback if exact match not found + let apiIndexToUse = apiConversationHistoryIndex + const tsThreshold = currentCline.clineMessages[messageIndex]?.ts + if (apiIndexToUse === -1 && typeof tsThreshold === "number") { + apiIndexToUse = findFirstApiIndexAtOrAfter(tsThreshold, currentCline) + } if (messageIndex === -1) { - const errorMessage = `Message with timestamp ${messageTs} not found` - console.error("[handleDeleteMessageConfirm]", errorMessage) - await vscode.window.showErrorMessage(errorMessage) + await vscode.window.showErrorMessage(t("common:errors.message.message_not_found", { messageTs })) return } @@ -188,7 +205,7 @@ export const webviewMessageHandler = async ( } // Delete this message and all subsequent messages - await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex) + await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiIndexToUse) // Restore checkpoint associations for preserved messages for (const [ts, checkpoint] of preservedCheckpoints) { @@ -204,11 +221,16 @@ export const webviewMessageHandler = async ( taskId: currentCline.taskId, globalStoragePath: provider.contextProxy.globalStorageUri.fsPath, }) + + // Update the UI to reflect the deletion + await provider.postStateToWebview() } } catch (error) { console.error("Error in delete message:", error) vscode.window.showErrorMessage( - `Error deleting message: ${error instanceof Error ? error.message : String(error)}`, + t("common:errors.message.error_deleting_message", { + error: error instanceof Error ? error.message : String(error), + }), ) } } @@ -265,7 +287,7 @@ export const webviewMessageHandler = async ( const { messageIndex, apiConversationHistoryIndex } = findMessageIndices(messageTs, currentCline) if (messageIndex === -1) { - const errorMessage = `Message with timestamp ${messageTs} not found` + const errorMessage = t("common:errors.message.message_not_found", { messageTs }) console.error("[handleEditMessageConfirm]", errorMessage) await vscode.window.showErrorMessage(errorMessage) return @@ -308,18 +330,49 @@ export const webviewMessageHandler = async ( } } - // For non-checkpoint edits, preserve checkpoint associations for remaining messages + // For non-checkpoint edits, remove the ORIGINAL user message being edited and all subsequent messages + // Determine the correct starting index to delete from (prefer the last preceding user_feedback message) + let deleteFromMessageIndex = messageIndex + let deleteFromApiIndex = apiConversationHistoryIndex + + // Find the nearest preceding user message to ensure we replace the original, not just the assistant reply + for (let i = messageIndex; i >= 0; i--) { + const m = currentCline.clineMessages[i] + if (m?.say === "user_feedback") { + deleteFromMessageIndex = i + // Align API history truncation to the same user message timestamp if present + const userTs = m.ts + if (typeof userTs === "number") { + const apiIdx = currentCline.apiConversationHistory.findIndex( + (am: ApiMessage) => am.ts === userTs, + ) + if (apiIdx !== -1) { + deleteFromApiIndex = apiIdx + } + } + break + } + } + + // Timestamp fallback for API history when exact user message isn't present + if (deleteFromApiIndex === -1) { + const tsThresholdForEdit = currentCline.clineMessages[deleteFromMessageIndex]?.ts + if (typeof tsThresholdForEdit === "number") { + deleteFromApiIndex = findFirstApiIndexAtOrAfter(tsThresholdForEdit, currentCline) + } + } + // Store checkpoints from messages that will be preserved const preservedCheckpoints = new Map() - for (let i = 0; i < messageIndex; i++) { + for (let i = 0; i < deleteFromMessageIndex; i++) { const msg = currentCline.clineMessages[i] if (msg?.checkpoint && msg.ts) { preservedCheckpoints.set(msg.ts, msg.checkpoint) } } - // Edit this message and delete subsequent - await removeMessagesThisAndSubsequent(currentCline, messageIndex, apiConversationHistoryIndex) + // Delete the original (user) message and all subsequent messages + await removeMessagesThisAndSubsequent(currentCline, deleteFromMessageIndex, deleteFromApiIndex) // Restore checkpoint associations for preserved messages for (const [ts, checkpoint] of preservedCheckpoints) { @@ -336,20 +389,16 @@ export const webviewMessageHandler = async ( globalStoragePath: provider.contextProxy.globalStorageUri.fsPath, }) - // Process the edited message as a regular user message - webviewMessageHandler(provider, { - type: "askResponse", - askResponse: "messageResponse", - text: editedContent, - images, - }) + // Update the UI to reflect the deletion + await provider.postStateToWebview() - // Don't initialize with history item for edit operations - // The webviewMessageHandler will handle the conversation state + await currentCline.submitUserMessage(editedContent, images) } catch (error) { console.error("Error in edit message:", error) vscode.window.showErrorMessage( - `Error editing message: ${error instanceof Error ? error.message : String(error)}`, + t("common:errors.message.error_editing_message", { + error: error instanceof Error ? error.message : String(error), + }), ) } } @@ -1451,9 +1500,17 @@ export const webviewMessageHandler = async ( } break case "deleteMessage": { - if (provider.getCurrentTask() && typeof message.value === "number" && message.value) { - await handleMessageModificationsOperation(message.value, "delete") + if (!provider.getCurrentTask()) { + await vscode.window.showErrorMessage(t("common:errors.message.no_active_task_to_delete")) + break + } + + if (typeof message.value !== "number" || !message.value) { + await vscode.window.showErrorMessage(t("common:errors.message.invalid_timestamp_for_deletion")) + break } + + await handleMessageModificationsOperation(message.value, "delete") break } case "submitEditedMessage": { @@ -1841,9 +1898,17 @@ export const webviewMessageHandler = async ( } break case "deleteMessageConfirm": - if (message.messageTs) { - await handleDeleteMessageConfirm(message.messageTs, message.restoreCheckpoint) + if (!message.messageTs) { + await vscode.window.showErrorMessage(t("common:errors.message.cannot_delete_missing_timestamp")) + break + } + + if (typeof message.messageTs !== "number") { + await vscode.window.showErrorMessage(t("common:errors.message.cannot_delete_invalid_timestamp")) + break } + + await handleDeleteMessageConfirm(message.messageTs, message.restoreCheckpoint) break case "editMessageConfirm": if (message.messageTs && message.text) { diff --git a/src/i18n/locales/ca/common.json b/src/i18n/locales/ca/common.json index 379b0d2311f3..a22850e1d673 100644 --- a/src/i18n/locales/ca/common.json +++ b/src/i18n/locales/ca/common.json @@ -90,6 +90,15 @@ "apiKeyModelPlanMismatch": "Les claus API i els plans de subscripció permeten models diferents. Assegura't que el model seleccionat estigui inclòs al teu pla.", "notFound": "No s'ha trobat l'executable Claude Code '{{claudePath}}'.\n\nInstal·la Claude Code CLI:\n1. Visita {{installationUrl}} per descarregar Claude Code\n2. Segueix les instruccions d'instal·lació per al teu sistema operatiu\n3. Assegura't que la comanda 'claude' estigui disponible al teu PATH\n4. Alternativament, configura una ruta personalitzada a la configuració de Roo sota 'Ruta de Claude Code'\n\nError original: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "No hi ha cap tasca activa de la qual eliminar missatges", + "invalid_timestamp_for_deletion": "Marca de temps del missatge no vàlida per a l'eliminació", + "cannot_delete_missing_timestamp": "No es pot eliminar el missatge: falta la marca de temps", + "cannot_delete_invalid_timestamp": "No es pot eliminar el missatge: marca de temps no vàlida", + "message_not_found": "Missatge amb marca de temps {{messageTs}} no trobat", + "error_deleting_message": "Error eliminant missatge: {{error}}", + "error_editing_message": "Error editant missatge: {{error}}" + }, "gemini": { "generate_stream": "Error del flux de context de generació de Gemini: {{error}}", "generate_complete_prompt": "Error de finalització de Gemini: {{error}}", diff --git a/src/i18n/locales/de/common.json b/src/i18n/locales/de/common.json index 1d52866e8379..02a9737eb059 100644 --- a/src/i18n/locales/de/common.json +++ b/src/i18n/locales/de/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "API-Schlüssel und Abonnement-Pläne erlauben verschiedene Modelle. Stelle sicher, dass das ausgewählte Modell in deinem Plan enthalten ist.", "notFound": "Claude Code ausführbare Datei '{{claudePath}}' nicht gefunden.\n\nBitte installiere Claude Code CLI:\n1. Besuche {{installationUrl}} um Claude Code herunterzuladen\n2. Folge den Installationsanweisungen für dein Betriebssystem\n3. Stelle sicher, dass der 'claude' Befehl in deinem PATH verfügbar ist\n4. Alternativ konfiguriere einen benutzerdefinierten Pfad in den Roo-Einstellungen unter 'Claude Code Pfad'\n\nUrsprünglicher Fehler: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "Keine aktive Aufgabe, aus der Nachrichten gelöscht werden können", + "invalid_timestamp_for_deletion": "Ungültiger Nachrichten-Zeitstempel zum Löschen", + "cannot_delete_missing_timestamp": "Nachricht kann nicht gelöscht werden: fehlender Zeitstempel", + "cannot_delete_invalid_timestamp": "Nachricht kann nicht gelöscht werden: ungültiger Zeitstempel", + "message_not_found": "Nachricht mit Zeitstempel {{messageTs}} nicht gefunden", + "error_deleting_message": "Fehler beim Löschen der Nachricht: {{error}}", + "error_editing_message": "Fehler beim Bearbeiten der Nachricht: {{error}}" + }, "gemini": { "generate_stream": "Fehler beim Generieren des Kontext-Streams von Gemini: {{error}}", "generate_complete_prompt": "Fehler bei der Vervollständigung durch Gemini: {{error}}", diff --git a/src/i18n/locales/en/common.json b/src/i18n/locales/en/common.json index 2ff203bbb5e5..55ce2016cab0 100644 --- a/src/i18n/locales/en/common.json +++ b/src/i18n/locales/en/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "No active task to delete messages from", + "invalid_timestamp_for_deletion": "Invalid message timestamp for deletion", + "cannot_delete_missing_timestamp": "Cannot delete message: missing timestamp", + "cannot_delete_invalid_timestamp": "Cannot delete message: invalid timestamp", + "message_not_found": "Message with timestamp {{messageTs}} not found", + "error_deleting_message": "Error deleting message: {{error}}", + "error_editing_message": "Error editing message: {{error}}" + }, "gemini": { "generate_stream": "Gemini generate context stream error: {{error}}", "generate_complete_prompt": "Gemini completion error: {{error}}", diff --git a/src/i18n/locales/es/common.json b/src/i18n/locales/es/common.json index f80efa1a95d4..7923c927f161 100644 --- a/src/i18n/locales/es/common.json +++ b/src/i18n/locales/es/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "Las claves API y los planes de suscripción permiten diferentes modelos. Asegúrate de que el modelo seleccionado esté incluido en tu plan.", "notFound": "Ejecutable de Claude Code '{{claudePath}}' no encontrado.\n\nPor favor instala Claude Code CLI:\n1. Visita {{installationUrl}} para descargar Claude Code\n2. Sigue las instrucciones de instalación para tu sistema operativo\n3. Asegúrate de que el comando 'claude' esté disponible en tu PATH\n4. Alternativamente, configura una ruta personalizada en la configuración de Roo bajo 'Ruta de Claude Code'\n\nError original: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "No hay tarea activa de la cual eliminar mensajes", + "invalid_timestamp_for_deletion": "Marca de tiempo del mensaje no válida para eliminación", + "cannot_delete_missing_timestamp": "No se puede eliminar el mensaje: falta marca de tiempo", + "cannot_delete_invalid_timestamp": "No se puede eliminar el mensaje: marca de tiempo no válida", + "message_not_found": "Mensaje con marca de tiempo {{messageTs}} no encontrado", + "error_deleting_message": "Error eliminando mensaje: {{error}}", + "error_editing_message": "Error editando mensaje: {{error}}" + }, "gemini": { "generate_stream": "Error del stream de contexto de generación de Gemini: {{error}}", "generate_complete_prompt": "Error de finalización de Gemini: {{error}}", diff --git a/src/i18n/locales/fr/common.json b/src/i18n/locales/fr/common.json index ecc0423e36bb..8a6a592fc837 100644 --- a/src/i18n/locales/fr/common.json +++ b/src/i18n/locales/fr/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "Les clés API et les plans d'abonnement permettent différents modèles. Assurez-vous que le modèle sélectionné est inclus dans votre plan.", "notFound": "Exécutable Claude Code '{{claudePath}}' introuvable.\n\nVeuillez installer Claude Code CLI :\n1. Visitez {{installationUrl}} pour télécharger Claude Code\n2. Suivez les instructions d'installation pour votre système d'exploitation\n3. Assurez-vous que la commande 'claude' est disponible dans votre PATH\n4. Alternativement, configurez un chemin personnalisé dans les paramètres Roo sous 'Chemin de Claude Code'\n\nErreur originale : {{originalError}}" }, + "message": { + "no_active_task_to_delete": "Aucune tâche active pour supprimer des messages", + "invalid_timestamp_for_deletion": "Horodatage du message invalide pour la suppression", + "cannot_delete_missing_timestamp": "Impossible de supprimer le message : horodatage manquant", + "cannot_delete_invalid_timestamp": "Impossible de supprimer le message : horodatage invalide", + "message_not_found": "Message avec horodatage {{messageTs}} introuvable", + "error_deleting_message": "Erreur lors de la suppression du message : {{error}}", + "error_editing_message": "Erreur lors de la modification du message : {{error}}" + }, "gemini": { "generate_stream": "Erreur du flux de contexte de génération Gemini : {{error}}", "generate_complete_prompt": "Erreur d'achèvement de Gemini : {{error}}", diff --git a/src/i18n/locales/hi/common.json b/src/i18n/locales/hi/common.json index 90dcb125d288..24f6f6cf6a2f 100644 --- a/src/i18n/locales/hi/common.json +++ b/src/i18n/locales/hi/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "संदेशों को हटाने के लिए कोई सक्रिय कार्य नहीं", + "invalid_timestamp_for_deletion": "हटाने के लिए अमान्य संदेश टाइमस्टैम्प", + "cannot_delete_missing_timestamp": "संदेश हटाया नहीं जा सकता: टाइमस्टैम्प गुम है", + "cannot_delete_invalid_timestamp": "संदेश हटाया नहीं जा सकता: अमान्य टाइमस्टैम्प", + "message_not_found": "टाइमस्टैम्प {{messageTs}} वाला संदेश नहीं मिला", + "error_deleting_message": "संदेश हटाने में त्रुटि: {{error}}", + "error_editing_message": "संदेश संपादित करने में त्रुटि: {{error}}" + }, "gemini": { "generate_stream": "जेमिनी जनरेट कॉन्टेक्स्ट स्ट्रीम त्रुटि: {{error}}", "generate_complete_prompt": "जेमिनी समापन त्रुटि: {{error}}", diff --git a/src/i18n/locales/id/common.json b/src/i18n/locales/id/common.json index e6b9d4f53631..51219d3c2681 100644 --- a/src/i18n/locales/id/common.json +++ b/src/i18n/locales/id/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "Tidak ada tugas aktif untuk menghapus pesan", + "invalid_timestamp_for_deletion": "Timestamp pesan tidak valid untuk penghapusan", + "cannot_delete_missing_timestamp": "Tidak dapat menghapus pesan: timestamp tidak ada", + "cannot_delete_invalid_timestamp": "Tidak dapat menghapus pesan: timestamp tidak valid", + "message_not_found": "Pesan dengan timestamp {{messageTs}} tidak ditemukan", + "error_deleting_message": "Error menghapus pesan: {{error}}", + "error_editing_message": "Error mengedit pesan: {{error}}" + }, "gemini": { "generate_stream": "Kesalahan aliran konteks pembuatan Gemini: {{error}}", "generate_complete_prompt": "Kesalahan penyelesaian Gemini: {{error}}", diff --git a/src/i18n/locales/it/common.json b/src/i18n/locales/it/common.json index 4b235ab54bc0..c3cb7e2626ec 100644 --- a/src/i18n/locales/it/common.json +++ b/src/i18n/locales/it/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "Nessuna attività attiva da cui eliminare messaggi", + "invalid_timestamp_for_deletion": "Timestamp del messaggio non valido per l'eliminazione", + "cannot_delete_missing_timestamp": "Impossibile eliminare il messaggio: timestamp mancante", + "cannot_delete_invalid_timestamp": "Impossibile eliminare il messaggio: timestamp non valido", + "message_not_found": "Messaggio con timestamp {{messageTs}} non trovato", + "error_deleting_message": "Errore durante l'eliminazione del messaggio: {{error}}", + "error_editing_message": "Errore durante la modifica del messaggio: {{error}}" + }, "gemini": { "generate_stream": "Errore del flusso di contesto di generazione Gemini: {{error}}", "generate_complete_prompt": "Errore di completamento Gemini: {{error}}", diff --git a/src/i18n/locales/ja/common.json b/src/i18n/locales/ja/common.json index bf023eca37e7..38fa18c6b07b 100644 --- a/src/i18n/locales/ja/common.json +++ b/src/i18n/locales/ja/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "メッセージを削除するアクティブなタスクがありません", + "invalid_timestamp_for_deletion": "削除用のメッセージタイムスタンプが無効です", + "cannot_delete_missing_timestamp": "メッセージを削除できません:タイムスタンプがありません", + "cannot_delete_invalid_timestamp": "メッセージを削除できません:タイムスタンプが無効です", + "message_not_found": "タイムスタンプ {{messageTs}} のメッセージが見つかりません", + "error_deleting_message": "メッセージ削除エラー:{{error}}", + "error_editing_message": "メッセージ編集エラー:{{error}}" + }, "gemini": { "generate_stream": "Gemini 生成コンテキスト ストリーム エラー: {{error}}", "generate_complete_prompt": "Gemini 完了エラー: {{error}}", diff --git a/src/i18n/locales/ko/common.json b/src/i18n/locales/ko/common.json index 33ae49ef2a9f..9b4f3292794b 100644 --- a/src/i18n/locales/ko/common.json +++ b/src/i18n/locales/ko/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "메시지를 삭제할 활성 작업이 없습니다", + "invalid_timestamp_for_deletion": "삭제를 위한 메시지 타임스탬프가 유효하지 않습니다", + "cannot_delete_missing_timestamp": "메시지를 삭제할 수 없습니다: 타임스탬프가 없습니다", + "cannot_delete_invalid_timestamp": "메시지를 삭제할 수 없습니다: 타임스탬프가 유효하지 않습니다", + "message_not_found": "타임스탬프 {{messageTs}}인 메시지를 찾을 수 없습니다", + "error_deleting_message": "메시지 삭제 오류: {{error}}", + "error_editing_message": "메시지 편집 오류: {{error}}" + }, "gemini": { "generate_stream": "Gemini 생성 컨텍스트 스트림 오류: {{error}}", "generate_complete_prompt": "Gemini 완료 오류: {{error}}", diff --git a/src/i18n/locales/nl/common.json b/src/i18n/locales/nl/common.json index 8c4526889b61..ded3202ce8d9 100644 --- a/src/i18n/locales/nl/common.json +++ b/src/i18n/locales/nl/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "Geen actieve taak om berichten uit te verwijderen", + "invalid_timestamp_for_deletion": "Ongeldig bericht tijdstempel voor verwijdering", + "cannot_delete_missing_timestamp": "Kan bericht niet verwijderen: tijdstempel ontbreekt", + "cannot_delete_invalid_timestamp": "Kan bericht niet verwijderen: ongeldig tijdstempel", + "message_not_found": "Bericht met tijdstempel {{messageTs}} niet gevonden", + "error_deleting_message": "Fout bij verwijderen van bericht: {{error}}", + "error_editing_message": "Fout bij bewerken van bericht: {{error}}" + }, "gemini": { "generate_stream": "Fout bij het genereren van contextstream door Gemini: {{error}}", "generate_complete_prompt": "Fout bij het voltooien door Gemini: {{error}}", diff --git a/src/i18n/locales/pl/common.json b/src/i18n/locales/pl/common.json index 35a09be166a8..f6ad0c204c17 100644 --- a/src/i18n/locales/pl/common.json +++ b/src/i18n/locales/pl/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "Brak aktywnego zadania do usunięcia wiadomości", + "invalid_timestamp_for_deletion": "Nieprawidłowy znacznik czasu wiadomości do usunięcia", + "cannot_delete_missing_timestamp": "Nie można usunąć wiadomości: brak znacznika czasu", + "cannot_delete_invalid_timestamp": "Nie można usunąć wiadomości: nieprawidłowy znacznik czasu", + "message_not_found": "Wiadomość ze znacznikiem czasu {{messageTs}} nie została znaleziona", + "error_deleting_message": "Błąd usuwania wiadomości: {{error}}", + "error_editing_message": "Błąd edytowania wiadomości: {{error}}" + }, "gemini": { "generate_stream": "Błąd strumienia kontekstu generowania Gemini: {{error}}", "generate_complete_prompt": "Błąd uzupełniania Gemini: {{error}}", diff --git a/src/i18n/locales/pt-BR/common.json b/src/i18n/locales/pt-BR/common.json index 4d687019ff4d..e717ceb991d1 100644 --- a/src/i18n/locales/pt-BR/common.json +++ b/src/i18n/locales/pt-BR/common.json @@ -91,6 +91,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "Nenhuma tarefa ativa para excluir mensagens", + "invalid_timestamp_for_deletion": "Timestamp da mensagem inválido para exclusão", + "cannot_delete_missing_timestamp": "Não é possível excluir mensagem: timestamp ausente", + "cannot_delete_invalid_timestamp": "Não é possível excluir mensagem: timestamp inválido", + "message_not_found": "Mensagem com timestamp {{messageTs}} não encontrada", + "error_deleting_message": "Erro ao excluir mensagem: {{error}}", + "error_editing_message": "Erro ao editar mensagem: {{error}}" + }, "gemini": { "generate_stream": "Erro de fluxo de contexto de geração do Gemini: {{error}}", "generate_complete_prompt": "Erro de conclusão do Gemini: {{error}}", diff --git a/src/i18n/locales/ru/common.json b/src/i18n/locales/ru/common.json index df864815fab8..7c9da67dfee2 100644 --- a/src/i18n/locales/ru/common.json +++ b/src/i18n/locales/ru/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "Нет активной задачи для удаления сообщений", + "invalid_timestamp_for_deletion": "Недействительная временная метка сообщения для удаления", + "cannot_delete_missing_timestamp": "Невозможно удалить сообщение: отсутствует временная метка", + "cannot_delete_invalid_timestamp": "Невозможно удалить сообщение: недействительная временная метка", + "message_not_found": "Сообщение с временной меткой {{messageTs}} не найдено", + "error_deleting_message": "Ошибка удаления сообщения: {{error}}", + "error_editing_message": "Ошибка редактирования сообщения: {{error}}" + }, "gemini": { "generate_stream": "Ошибка потока контекста генерации Gemini: {{error}}", "generate_complete_prompt": "Ошибка завершения Gemini: {{error}}", diff --git a/src/i18n/locales/tr/common.json b/src/i18n/locales/tr/common.json index 5735d7f1ca9f..8a49fd6f24a6 100644 --- a/src/i18n/locales/tr/common.json +++ b/src/i18n/locales/tr/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "Mesaj silinecek aktif görev yok", + "invalid_timestamp_for_deletion": "Silme için geçersiz mesaj zaman damgası", + "cannot_delete_missing_timestamp": "Mesaj silinemiyor: zaman damgası eksik", + "cannot_delete_invalid_timestamp": "Mesaj silinemiyor: geçersiz zaman damgası", + "message_not_found": "{{messageTs}} zaman damgalı mesaj bulunamadı", + "error_deleting_message": "Mesaj silme hatası: {{error}}", + "error_editing_message": "Mesaj düzenleme hatası: {{error}}" + }, "gemini": { "generate_stream": "Gemini oluşturma bağlam akışı hatası: {{error}}", "generate_complete_prompt": "Gemini tamamlama hatası: {{error}}", diff --git a/src/i18n/locales/vi/common.json b/src/i18n/locales/vi/common.json index d11f91f29457..942f29eb5a0c 100644 --- a/src/i18n/locales/vi/common.json +++ b/src/i18n/locales/vi/common.json @@ -87,6 +87,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "Không có nhiệm vụ hoạt động để xóa tin nhắn", + "invalid_timestamp_for_deletion": "Dấu thời gian tin nhắn không hợp lệ để xóa", + "cannot_delete_missing_timestamp": "Không thể xóa tin nhắn: thiếu dấu thời gian", + "cannot_delete_invalid_timestamp": "Không thể xóa tin nhắn: dấu thời gian không hợp lệ", + "message_not_found": "Không tìm thấy tin nhắn có dấu thời gian {{messageTs}}", + "error_deleting_message": "Lỗi xóa tin nhắn: {{error}}", + "error_editing_message": "Lỗi chỉnh sửa tin nhắn: {{error}}" + }, "gemini": { "generate_stream": "Lỗi luồng ngữ cảnh tạo Gemini: {{error}}", "generate_complete_prompt": "Lỗi hoàn thành Gemini: {{error}}", diff --git a/src/i18n/locales/zh-CN/common.json b/src/i18n/locales/zh-CN/common.json index e64339cecd12..1b3ae2f9c561 100644 --- a/src/i18n/locales/zh-CN/common.json +++ b/src/i18n/locales/zh-CN/common.json @@ -92,6 +92,15 @@ "apiKeyModelPlanMismatch": "API keys and subscription plans allow different models. Make sure the selected model is included in your plan.", "notFound": "Claude Code executable '{{claudePath}}' not found.\n\nPlease install Claude Code CLI:\n1. Visit {{installationUrl}} to download Claude Code\n2. Follow the installation instructions for your operating system\n3. Ensure the 'claude' command is available in your PATH\n4. Alternatively, configure a custom path in Roo settings under 'Claude Code Path'\n\nOriginal error: {{originalError}}" }, + "message": { + "no_active_task_to_delete": "没有可删除消息的活跃任务", + "invalid_timestamp_for_deletion": "删除操作的消息时间戳无效", + "cannot_delete_missing_timestamp": "无法删除消息:缺少时间戳", + "cannot_delete_invalid_timestamp": "无法删除消息:时间戳无效", + "message_not_found": "未找到时间戳为 {{messageTs}} 的消息", + "error_deleting_message": "删除消息时出错:{{error}}", + "error_editing_message": "编辑消息时出错:{{error}}" + }, "gemini": { "generate_stream": "Gemini 生成上下文流错误:{{error}}", "generate_complete_prompt": "Gemini 完成错误:{{error}}", diff --git a/src/i18n/locales/zh-TW/common.json b/src/i18n/locales/zh-TW/common.json index ddf283cf6ca6..b59dbfa38872 100644 --- a/src/i18n/locales/zh-TW/common.json +++ b/src/i18n/locales/zh-TW/common.json @@ -86,6 +86,15 @@ "apiKeyModelPlanMismatch": "API 金鑰和訂閱方案允許不同的模型。請確保所選模型包含在您的方案中。", "notFound": "找不到 Claude Code 可執行檔案 '{{claudePath}}'。\n\n請安裝 Claude Code CLI:\n1. 造訪 {{installationUrl}} 下載 Claude Code\n2. 依照作業系統的安裝說明進行操作\n3. 確保 'claude' 指令在 PATH 中可用\n4. 或者在 Roo 設定中的 'Claude Code 路徑' 下設定自訂路徑\n\n原始錯誤:{{originalError}}" }, + "message": { + "no_active_task_to_delete": "沒有可刪除訊息的活躍工作", + "invalid_timestamp_for_deletion": "刪除操作的訊息時間戳無效", + "cannot_delete_missing_timestamp": "無法刪除訊息:缺少時間戳", + "cannot_delete_invalid_timestamp": "無法刪除訊息:時間戳無效", + "message_not_found": "未找到時間戳為 {{messageTs}} 的訊息", + "error_deleting_message": "刪除訊息時出錯:{{error}}", + "error_editing_message": "編輯訊息時出錯:{{error}}" + }, "gemini": { "generate_stream": "Gemini 產生內容串流錯誤:{{error}}", "generate_complete_prompt": "Gemini 完成錯誤:{{error}}", From 3a589f4667900f498d0007aa200967d86c297f2e Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Tue, 9 Sep 2025 11:42:04 -0500 Subject: [PATCH 08/20] fix: add GIT_EDITOR env var to merge-resolver mode for non-interactive rebase (#7819) --- .roo/rules-merge-resolver/1_workflow.xml | 21 ++++++- .roo/rules-merge-resolver/3_tool_usage.xml | 57 +++++++++++++++++-- .../4_complete_example.xml | 7 ++- 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/.roo/rules-merge-resolver/1_workflow.xml b/.roo/rules-merge-resolver/1_workflow.xml index a63809db70e9..2f0d1162f644 100644 --- a/.roo/rules-merge-resolver/1_workflow.xml +++ b/.roo/rules-merge-resolver/1_workflow.xml @@ -30,12 +30,13 @@ gh pr checkout [PR_NUMBER] --force git fetch origin main - git rebase origin/main + GIT_EDITOR=true git rebase origin/main
Force checkout the PR branch to ensure clean state Fetch the latest main branch Attempt to rebase onto main to reveal conflicts + Use GIT_EDITOR=true to ensure non-interactive rebase
@@ -108,8 +109,8 @@ - git rebase origin/main - Rebase current branch onto main to reveal conflicts + GIT_EDITOR=true git rebase origin/main + Rebase current branch onto main to reveal conflicts (non-interactive) @@ -133,6 +134,20 @@ + + GIT_EDITOR=true git rebase --continue + Continue rebase after resolving conflicts (non-interactive) + + + + + + true + Set to 'true' (a no-op command) to prevent interactive prompts during rebase operations + Prefix git rebase commands with GIT_EDITOR=true to ensure non-interactive execution + + + All merge conflicts have been resolved Resolved files have been staged diff --git a/.roo/rules-merge-resolver/3_tool_usage.xml b/.roo/rules-merge-resolver/3_tool_usage.xml index 35f3b5da7596..30e74955744c 100644 --- a/.roo/rules-merge-resolver/3_tool_usage.xml +++ b/.roo/rules-merge-resolver/3_tool_usage.xml @@ -26,6 +26,8 @@ Chain git commands with && for efficiency Use --format options for structured output Capture command output for parsing + Use GIT_EDITOR=true for non-interactive git rebase operations + Set environment variables inline to avoid prompts during automation @@ -46,7 +48,7 @@ Rebase onto main to reveal conflicts - git rebase origin/main + GIT_EDITOR=true git rebase origin/main @@ -71,7 +73,7 @@ Continue rebase after resolution - git rebase --continue + GIT_EDITOR=true git rebase --continue @@ -152,7 +154,7 @@ const config = { execute_command - Get PR info with gh CLI execute_command - Checkout PR with gh pr checkout --force execute_command - Fetch origin main - execute_command - Rebase onto origin/main + execute_command - Rebase onto origin/main with GIT_EDITOR=true execute_command - Check for conflicts with git status @@ -178,13 +180,22 @@ const config = { execute_command - Check all conflicts resolved - execute_command - Continue rebase with git rebase --continue + execute_command - Continue rebase with GIT_EDITOR=true git rebase --continue execute_command - Verify clean status + + Git commands waiting for interactive input + + Use GIT_EDITOR=true to bypass editor prompts + Set GIT_SEQUENCE_EDITOR=true for sequence editing + Consider --no-edit flag for commit operations + + + Rebase completes without conflicts @@ -225,4 +236,42 @@ const config = { + + + + Ensuring git operations run without requiring user interaction is critical + for automated conflict resolution. The mode uses environment variables to + bypass interactive prompts. + + + + + Set to 'true' (a no-op command) to skip editor prompts + GIT_EDITOR=true git rebase --continue + During rebase operations that would normally open an editor + + + + Skip interactive rebase todo editing + GIT_SEQUENCE_EDITOR=true git rebase -i HEAD~3 + When interactive rebase is triggered but no editing needed + + + + Use flags to avoid interactive prompts + + git commit --no-edit (use existing message) + git merge --no-edit (skip merge message editing) + git cherry-pick --no-edit (keep original message) + + + + + + Always test commands locally first to identify potential prompts + Combine environment variables when multiple editors might be invoked + Document why non-interactive mode is used in comments + Have fallback strategies if automation fails + + \ No newline at end of file diff --git a/.roo/rules-merge-resolver/4_complete_example.xml b/.roo/rules-merge-resolver/4_complete_example.xml index dae858799717..32b2bf344bc8 100644 --- a/.roo/rules-merge-resolver/4_complete_example.xml +++ b/.roo/rules-merge-resolver/4_complete_example.xml @@ -54,7 +54,7 @@ From github.com:user/repo -git rebase origin/main +GIT_EDITOR=true git rebase origin/main ]]> -git rebase --continue +GIT_EDITOR=true git rebase --continue ]]> Use git blame and commit messages to understand the history Combine non-conflicting improvements when possible Prioritize bugfixes while accommodating refactors - Complete the rebase process with git rebase --continue + Use GIT_EDITOR=true to ensure non-interactive rebase operations + Complete the rebase process with GIT_EDITOR=true git rebase --continue Validate that both sets of changes work together \ No newline at end of file From 8f14912c069edf2f47d1d302a91c9b678366718b Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 13:18:33 -0400 Subject: [PATCH 09/20] UI: Render reasoning as plain italic (match ) (#7752) Co-authored-by: Roo Code Co-authored-by: Hannes Rudolph Co-authored-by: daniel-lxs --- webview-ui/src/components/chat/ChatRow.tsx | 8 +- .../src/components/chat/ReasoningBlock.tsx | 113 ++++++------------ 2 files changed, 41 insertions(+), 80 deletions(-) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index f24e2ca6602f..23ec50af37d5 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -118,7 +118,6 @@ export const ChatRowContent = ({ const { mcpServers, alwaysAllowMcp, currentCheckpoint, mode, apiConfiguration } = useExtensionState() const { info: model } = useSelectedModel(apiConfiguration) - const [reasoningCollapsed, setReasoningCollapsed] = useState(true) const [isDiffErrorExpanded, setIsDiffErrorExpanded] = useState(false) const [showCopySuccess, setShowCopySuccess] = useState(false) const [isEditing, setIsEditing] = useState(false) @@ -1087,9 +1086,10 @@ export const ChatRowContent = ({ return ( setReasoningCollapsed(!reasoningCollapsed)} + ts={message.ts} + isStreaming={isStreaming} + isLast={isLast} + metadata={message.metadata as any} /> ) case "api_req_started": diff --git a/webview-ui/src/components/chat/ReasoningBlock.tsx b/webview-ui/src/components/chat/ReasoningBlock.tsx index baa93485f9fd..3c981126ef98 100644 --- a/webview-ui/src/components/chat/ReasoningBlock.tsx +++ b/webview-ui/src/components/chat/ReasoningBlock.tsx @@ -1,96 +1,57 @@ -import { useCallback, useEffect, useRef, useState } from "react" -import { CaretDownIcon, CaretUpIcon, CounterClockwiseClockIcon } from "@radix-ui/react-icons" +import React, { useEffect, useRef, useState } from "react" import { useTranslation } from "react-i18next" import MarkdownBlock from "../common/MarkdownBlock" -import { useMount } from "react-use" +import { Clock, Lightbulb } from "lucide-react" interface ReasoningBlockProps { content: string - elapsed?: number - isCollapsed?: boolean - onToggleCollapse?: () => void + ts: number + isStreaming: boolean + isLast: boolean + metadata?: any } -export const ReasoningBlock = ({ content, elapsed, isCollapsed = false, onToggleCollapse }: ReasoningBlockProps) => { - const contentRef = useRef(null) - const elapsedRef = useRef(0) - const { t } = useTranslation("chat") - const [thought, setThought] = useState() - const [prevThought, setPrevThought] = useState(t("chat:reasoning.thinking")) - const [isTransitioning, setIsTransitioning] = useState(false) - const cursorRef = useRef(0) - const queueRef = useRef([]) +/** + * Render reasoning with a heading and a simple timer. + * - Heading uses i18n key chat:reasoning.thinking + * - Timer runs while reasoning is active (no persistence) + */ +export const ReasoningBlock = ({ content, isStreaming, isLast }: ReasoningBlockProps) => { + const { t } = useTranslation() - useEffect(() => { - if (contentRef.current && !isCollapsed) { - contentRef.current.scrollTop = contentRef.current.scrollHeight - } - }, [content, isCollapsed]) - - useEffect(() => { - if (elapsed) { - elapsedRef.current = elapsed - } - }, [elapsed]) - - // Process the transition queue. - const processNextTransition = useCallback(() => { - const nextThought = queueRef.current.pop() - queueRef.current = [] - - if (nextThought) { - setIsTransitioning(true) - } - - setTimeout(() => { - if (nextThought) { - setPrevThought(nextThought) - setIsTransitioning(false) - } - - setTimeout(() => processNextTransition(), 500) - }, 200) - }, []) - - useMount(() => { - processNextTransition() - }) + const startTimeRef = useRef(Date.now()) + const [elapsed, setElapsed] = useState(0) + // Simple timer that runs while streaming useEffect(() => { - if (content.length - cursorRef.current > 160) { - setThought("... " + content.slice(cursorRef.current)) - cursorRef.current = content.length + if (isLast && isStreaming) { + const tick = () => setElapsed(Date.now() - startTimeRef.current) + tick() + const id = setInterval(tick, 1000) + return () => clearInterval(id) } - }, [content]) + }, [isLast, isStreaming]) - useEffect(() => { - if (thought && thought !== prevThought) { - queueRef.current.push(thought) - } - }, [thought, prevThought]) + const seconds = Math.floor(elapsed / 1000) + const secondsLabel = t("chat:reasoning.seconds", { count: seconds }) return ( -
-
-
- {prevThought} -
-
- {elapsedRef.current > 1000 && ( - <> - -
{t("reasoning.seconds", { count: Math.round(elapsedRef.current / 1000) })}
- - )} - {isCollapsed ? : } +
+
+
+ + {t("chat:reasoning.thinking")}
+ {elapsed > 0 && ( + + + {secondsLabel} + + )}
- {!isCollapsed && ( -
+ {(content?.trim()?.length ?? 0) > 0 && ( +
)} From 2f39ba01524eee002d8f8154b40ae1a52c89ab06 Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 13:53:25 -0700 Subject: [PATCH 10/20] Add taskSyncEnabled to userSettingsConfigSchema (#7827) feat: add taskSyncEnabled to userSettingsConfigSchema Co-authored-by: Roo Code --- packages/types/src/cloud.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/types/src/cloud.ts b/packages/types/src/cloud.ts index 827ec2d7da0e..a566e4ec6ab0 100644 --- a/packages/types/src/cloud.ts +++ b/packages/types/src/cloud.ts @@ -162,6 +162,7 @@ export type UserFeatures = z.infer export const userSettingsConfigSchema = z.object({ extensionBridgeEnabled: z.boolean().optional(), + taskSyncEnabled: z.boolean().optional(), }) export type UserSettingsConfig = z.infer From 0454dad523a5422bd505f0171150a457e769f466 Mon Sep 17 00:00:00 2001 From: John Richmond <5629+jr@users.noreply.github.com> Date: Tue, 9 Sep 2025 14:08:05 -0700 Subject: [PATCH 11/20] Release: v1.75.0 (#7829) chore: bump version to v1.75.0 --- packages/types/npm/package.metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/npm/package.metadata.json b/packages/types/npm/package.metadata.json index ab92224e727a..f6140c397757 100644 --- a/packages/types/npm/package.metadata.json +++ b/packages/types/npm/package.metadata.json @@ -1,6 +1,6 @@ { "name": "@roo-code/types", - "version": "1.74.0", + "version": "1.75.0", "description": "TypeScript type definitions for Roo Code.", "publishConfig": { "access": "public", From ddc1f3d5dcd18402ad196ebf7ca0d2c98e2cf65f Mon Sep 17 00:00:00 2001 From: "roomote[bot]" <219738659+roomote[bot]@users.noreply.github.com> Date: Tue, 9 Sep 2025 17:50:37 -0400 Subject: [PATCH 12/20] fix: prevent negative cost values and improve label visibility in evals chart (#7830) Co-authored-by: Roo Code --- apps/web-roo-code/src/app/evals/plot.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web-roo-code/src/app/evals/plot.tsx b/apps/web-roo-code/src/app/evals/plot.tsx index 86c1be3a9a34..f68007cd124b 100644 --- a/apps/web-roo-code/src/app/evals/plot.tsx +++ b/apps/web-roo-code/src/app/evals/plot.tsx @@ -175,13 +175,13 @@ export const Plot = ({ tableData }: PlotProps) => { <>
Cost x Score
- + Math.round((dataMin - 5) / 5) * 5, + (dataMin: number) => Math.max(0, Math.round((dataMin - 5) / 5) * 5), (dataMax: number) => Math.round((dataMax + 5) / 5) * 5, ]} tickFormatter={(value) => formatCurrency(value)} From 57a2105130dd06c53ae464fba81fc56746e49cbe Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Wed, 10 Sep 2025 00:50:57 -0400 Subject: [PATCH 13/20] Fix Groq context window display (#7839) --- src/api/providers/__tests__/groq.spec.ts | 2 +- src/api/providers/groq.ts | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/api/providers/__tests__/groq.spec.ts b/src/api/providers/__tests__/groq.spec.ts index 66bf0690a8fe..2aee4ea05268 100644 --- a/src/api/providers/__tests__/groq.spec.ts +++ b/src/api/providers/__tests__/groq.spec.ts @@ -149,7 +149,7 @@ describe("GroqHandler", () => { expect(firstChunk.done).toBe(false) expect(firstChunk.value).toMatchObject({ type: "usage", - inputTokens: 70, // 100 total - 30 cached + inputTokens: 100, outputTokens: 50, cacheWriteTokens: 0, cacheReadTokens: 30, diff --git a/src/api/providers/groq.ts b/src/api/providers/groq.ts index de07f7c46fcb..b66e42d7f016 100644 --- a/src/api/providers/groq.ts +++ b/src/api/providers/groq.ts @@ -66,20 +66,9 @@ export class GroqHandler extends BaseOpenAiCompatibleProvider { // Calculate cost using OpenAI-compatible cost calculation const totalCost = calculateApiCostOpenAI(info, inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens) - // Calculate non-cached input tokens for proper reporting - const nonCachedInputTokens = Math.max(0, inputTokens - cacheReadTokens - cacheWriteTokens) - - console.log("usage", { - inputTokens: nonCachedInputTokens, - outputTokens, - cacheWriteTokens, - cacheReadTokens, - totalCost, - }) - yield { type: "usage", - inputTokens: nonCachedInputTokens, + inputTokens, outputTokens, cacheWriteTokens, cacheReadTokens, From 6c3287d3060876a6ad82c90dda8af6e6c6054d2a Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Thu, 11 Sep 2025 12:47:49 +0100 Subject: [PATCH 14/20] Makes trigger label simpler and dynamic --- .../components/chat/AutoApproveDropdown.tsx | 62 +++++++++---------- .../src/components/chat/ChatTextArea.tsx | 2 +- webview-ui/src/i18n/locales/ca/chat.json | 13 ++-- webview-ui/src/i18n/locales/de/chat.json | 13 ++-- webview-ui/src/i18n/locales/en/chat.json | 6 +- webview-ui/src/i18n/locales/es/chat.json | 11 +++- webview-ui/src/i18n/locales/fr/chat.json | 15 +++-- webview-ui/src/i18n/locales/hi/chat.json | 13 ++-- webview-ui/src/i18n/locales/id/chat.json | 11 +++- webview-ui/src/i18n/locales/it/chat.json | 17 +++-- webview-ui/src/i18n/locales/ja/chat.json | 11 +++- webview-ui/src/i18n/locales/ko/chat.json | 15 +++-- webview-ui/src/i18n/locales/nl/chat.json | 13 ++-- webview-ui/src/i18n/locales/pl/chat.json | 15 +++-- webview-ui/src/i18n/locales/pt-BR/chat.json | 13 ++-- webview-ui/src/i18n/locales/ru/chat.json | 19 ++++-- webview-ui/src/i18n/locales/tr/chat.json | 15 +++-- webview-ui/src/i18n/locales/vi/chat.json | 17 +++-- webview-ui/src/i18n/locales/zh-CN/chat.json | 13 ++-- webview-ui/src/i18n/locales/zh-TW/chat.json | 15 +++-- 20 files changed, 198 insertions(+), 111 deletions(-) diff --git a/webview-ui/src/components/chat/AutoApproveDropdown.tsx b/webview-ui/src/components/chat/AutoApproveDropdown.tsx index 64366dbf547b..9c8cb4d941bc 100644 --- a/webview-ui/src/components/chat/AutoApproveDropdown.tsx +++ b/webview-ui/src/components/chat/AutoApproveDropdown.tsx @@ -1,5 +1,5 @@ import React from "react" -import { Stamp, ListChecks, LayoutList, Settings } from "lucide-react" +import { ListChecks, LayoutList, Settings, CheckCheck } from "lucide-react" import { vscode } from "@/utils/vscode" import { cn } from "@/lib/utils" @@ -9,15 +9,13 @@ import { useRooPortal } from "@/components/ui/hooks/useRooPortal" import { Popover, PopoverContent, PopoverTrigger, StandardTooltip } from "@/components/ui" import { AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle" import { useAutoApprovalToggles } from "@/hooks/useAutoApprovalToggles" -import { useAutoApprovalState } from "@/hooks/useAutoApprovalState" interface AutoApproveDropdownProps { disabled?: boolean - title?: string triggerClassName?: string } -export const AutoApproveDropdown = ({ disabled = false, title, triggerClassName = "" }: AutoApproveDropdownProps) => { +export const AutoApproveDropdown = ({ disabled = false, triggerClassName = "" }: AutoApproveDropdownProps) => { const [open, setOpen] = React.useState(false) const portalContainer = useRooPortal("roo-portal") const { t } = useAppTranslation() @@ -49,8 +47,6 @@ export const AutoApproveDropdown = ({ disabled = false, title, triggerClassName [baseToggles, alwaysApproveResubmit], ) - const { hasEnabledOptions, effectiveAutoApprovalEnabled } = useAutoApprovalState(toggles, autoApprovalEnabled) - const onAutoApproveToggle = React.useCallback( (key: AutoApproveSetting, value: boolean) => { vscode.postMessage({ type: key, bool: value }) @@ -141,18 +137,14 @@ export const AutoApproveDropdown = ({ disabled = false, title, triggerClassName [], ) - // Create display text for enabled actions - const displayText = React.useMemo(() => { - if (!effectiveAutoApprovalEnabled || !hasEnabledOptions) { - return t("chat:autoApprove.none") - } - const enabledActionsList = Object.entries(toggles) - .filter(([_key, value]) => !!value) - .map(([key]) => t(autoApproveSettingsConfig[key as AutoApproveSetting].labelKey)) - .join(", ") + // Calculate enabled and total counts as separate properties + const enabledCount = React.useMemo(() => { + return Object.values(toggles).filter((value) => !!value).length + }, [toggles]) - return enabledActionsList || t("chat:autoApprove.none") - }, [effectiveAutoApprovalEnabled, hasEnabledOptions, toggles, t]) + const totalCount = React.useMemo(() => { + return Object.keys(toggles).length + }, [toggles]) // Split settings into two columns const settingsArray = Object.values(autoApproveSettingsConfig) @@ -162,23 +154,25 @@ export const AutoApproveDropdown = ({ disabled = false, title, triggerClassName return ( - - - - {displayText} - - + + + + {enabledCount === totalCount + ? t("chat:autoApprove.triggerLabelAll") + : t("chat:autoApprove.triggerLabel", { count: enabledCount })} + + ( />
- +
diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 1d20be0c4077..407284fb1afe 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -245,12 +245,17 @@ "issues": "Sembla que estàs tenint problemes amb Windows PowerShell, si us plau consulta aquesta documentació per a més informació." }, "autoApprove": { - "title": "Aprovació automàtica:", + "title": "Aprovació automàtica", + "all": "Totes", "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. Configuració més detallada disponible a la Configuració.", - "selectOptionsFirst": "Selecciona almenys una opció a continuació per activar l'aprovació automàtica", + "description": "Executeu aquestes accions sense demanar permís. Activeu-ho només per a les accions en què confieu plenament.", + "selectOptionsFirst": "Seleccioneu almenys una opció a continuació per activar l'aprovació automàtica", "toggleAriaLabel": "Commuta l'aprovació automàtica", - "disabledAriaLabel": "Aprovació automàtica desactivada: seleccioneu primer les opcions" + "disabledAriaLabel": "Aprovació automàtica desactivada: seleccioneu primer les opcions", + "triggerLabel_zero": "Sense aprovació automàtica", + "triggerLabel_one": "1 aprovació automàtica", + "triggerLabel_other": "{{count}} aprovacions automàtiques", + "triggerLabelAll": "Totes les aprovacions automàtiques" }, "reasoning": { "thinking": "Pensant", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 82f1c77fbf1f..4a9d46559fa9 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -245,12 +245,17 @@ "issues": "Es scheint, dass du Probleme mit Windows PowerShell hast, bitte sieh dir dies an" }, "autoApprove": { - "title": "Automatische Genehmigung:", + "title": "Automatische Genehmigung", + "all": "Alle", "none": "Keine", - "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.", - "selectOptionsFirst": "Wähle mindestens eine der folgenden Optionen aus, um die automatische Genehmigung zu aktivieren", + "description": "Führe diese Aktionen aus, ohne um Erlaubnis zu fragen. Aktiviere dies nur für Aktionen, denen du voll vertraust.", + "selectOptionsFirst": "Wähle zuerst mindestens eine Option unten aus, um die automatische Genehmigung zu aktivieren", "toggleAriaLabel": "Automatische Genehmigung umschalten", - "disabledAriaLabel": "Automatische Genehmigung deaktiviert - zuerst Optionen auswählen" + "disabledAriaLabel": "Automatische Genehmigung deaktiviert - wähle zuerst Optionen aus", + "triggerLabel_zero": "Keine automatische Genehmigung", + "triggerLabel_one": "1 automatisch genehmigt", + "triggerLabel_other": "{{count}} automatisch genehmigt", + "triggerLabelAll": "Alle automatisch genehmigen" }, "reasoning": { "thinking": "Denke nach", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index c68b3695bbee..6fc6e5ce1b0f 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -275,7 +275,11 @@ "description": "Run these actions without asking for permission. Only enable for actions you fully trust.", "selectOptionsFirst": "Select at least one option below to enable auto-approval", "toggleAriaLabel": "Toggle auto-approval", - "disabledAriaLabel": "Auto-approval disabled - select options first" + "disabledAriaLabel": "Auto-approval disabled - select options first", + "triggerLabel_zero": "No auto-approve", + "triggerLabel_one": "1 auto-approved", + "triggerLabel_other": "{{count}} auto-approved", + "triggerLabelAll": "YOLO" }, "announcement": { "title": "🎉 Roo Code {{version}} Released", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index e63731b0954d..86815e685f88 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -245,12 +245,17 @@ "issues": "Parece que estás teniendo problemas con Windows PowerShell, por favor consulta esta" }, "autoApprove": { - "title": "Auto-aprobar:", + "title": "Aprobación automática", + "all": "Todo", "none": "Ninguno", - "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.", + "description": "Ejecuta estas acciones sin pedir permiso. Habilita esto solo para acciones en las que confíes plenamente.", "selectOptionsFirst": "Selecciona al menos una opción a continuación para habilitar la aprobación automática", "toggleAriaLabel": "Alternar aprobación automática", - "disabledAriaLabel": "Aprobación automática desactivada: seleccione primero las opciones" + "disabledAriaLabel": "Aprobación automática deshabilitada - selecciona primero las opciones", + "triggerLabel_zero": "Sin aprobación automática", + "triggerLabel_one": "1 aprobado automáticamente", + "triggerLabel_other": "{{count}} aprobados automáticamente", + "triggerLabelAll": "Aprobar todo automáticamente" }, "reasoning": { "thinking": "Pensando", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 257548978729..e10e0fc220fc 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -245,12 +245,17 @@ "issues": "Il semble que vous rencontriez des problèmes avec Windows PowerShell, veuillez consulter ce" }, "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. Configuration plus détaillée disponible dans les Paramètres.", - "selectOptionsFirst": "Sélectionnez au moins une option ci-dessous pour activer l'auto-approbation", + "title": "Approbation automatique", + "all": "Tout", + "none": "Aucun", + "description": "Exécutez ces actions sans demander la permission. N'activez cette option que pour les actions en lesquelles vous avez entièrement confiance.", + "selectOptionsFirst": "Sélectionnez au moins une option ci-dessous pour activer l'approbation automatique", "toggleAriaLabel": "Activer/désactiver l'approbation automatique", - "disabledAriaLabel": "Approbation automatique désactivée - sélectionnez d'abord les options" + "disabledAriaLabel": "Approbation automatique désactivée - sélectionnez d'abord les options", + "triggerLabel_zero": "Pas d'approbation automatique", + "triggerLabel_one": "1 approuvé automatiquement", + "triggerLabel_other": "{{count}} approuvés automatiquement", + "triggerLabelAll": "Tout approuver automatiquement" }, "reasoning": { "thinking": "Réflexion", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 28fc26fcaf46..873403ef0fdf 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -245,12 +245,17 @@ "issues": "ऐसा लगता है कि आपको Windows PowerShell के साथ समस्याएँ हो रही हैं, कृपया इसे देखें" }, "autoApprove": { - "title": "स्वत:-स्वीकृति:", + "title": "स्वतः-अनुमोदन", + "all": "सभी", "none": "कोई नहीं", - "description": "स्वत:-स्वीकृति Roo Code को अनुमति मांगे बिना क्रियाएँ करने की अनुमति देती है। केवल उन क्रियाओं के लिए सक्षम करें जिन पर आप पूरी तरह से विश्वास करते हैं। अधिक विस्तृत कॉन्फ़िगरेशन सेटिंग्स में उपलब्ध है।", - "selectOptionsFirst": "स्वतः-अनुमोदन सक्षम करने के लिए नीचे दिए گئے विकल्पों में से कम से कम एक का चयन करें", + "description": "अनुमति मांगे बिना इन क्रियाओं को चलाएं। इसे केवल उन क्रियाओं के लिए सक्षम करें जिन पर आप पूरी तरह भरोसा करते हैं।", + "selectOptionsFirst": "स्वतः-अनुमोदन सक्षम करने के लिए नीचे दिए गए कम से कम एक विकल्प का चयन करें", "toggleAriaLabel": "स्वतः-अनुमोदन टॉगल करें", - "disabledAriaLabel": "स्वतः-अनुमोदन अक्षम - पहले विकल्प चुनें" + "disabledAriaLabel": "स्वतः-अनुमोदन अक्षम - पहले विकल्प चुनें", + "triggerLabel_zero": "कोई स्वतः-अनुमोदन नहीं", + "triggerLabel_one": "1 स्वतः-अनुमोदित", + "triggerLabel_other": "{{count}} स्वतः-अनुमोदित", + "triggerLabelAll": "सभी को स्वतः-स्वीकृत करें" }, "reasoning": { "thinking": "विचार कर रहा है", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 0425a02b8f26..1c593b013aac 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -272,12 +272,17 @@ "issues": "Sepertinya kamu mengalami masalah Windows PowerShell, silakan lihat ini" }, "autoApprove": { - "title": "Auto-approve:", + "title": "Persetujuan otomatis", + "all": "Semua", "none": "Tidak Ada", - "description": "Auto-approve memungkinkan Roo Code melakukan aksi tanpa meminta izin. Hanya aktifkan untuk aksi yang benar-benar kamu percayai. Konfigurasi lebih detail tersedia di Pengaturan.", + "description": "Jalankan tindakan ini tanpa meminta izin. Aktifkan hanya untuk tindakan yang Anda percayai sepenuhnya.", "selectOptionsFirst": "Pilih setidaknya satu opsi di bawah untuk mengaktifkan persetujuan otomatis", "toggleAriaLabel": "Beralih persetujuan otomatis", - "disabledAriaLabel": "Persetujuan otomatis dinonaktifkan - pilih opsi terlebih dahulu" + "disabledAriaLabel": "Persetujuan otomatis dinonaktifkan - pilih opsi terlebih dahulu", + "triggerLabel_zero": "Tidak ada persetujuan otomatis", + "triggerLabel_one": "1 disetujui otomatis", + "triggerLabel_other": "{{count}} disetujui otomatis", + "triggerLabelAll": "Setujui Semua Secara Otomatis" }, "announcement": { "title": "🎉 Roo Code {{version}} Dirilis", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 4dd1270e3498..a101a72bd03e 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -245,12 +245,17 @@ "issues": "Sembra che tu stia avendo problemi con Windows PowerShell, consulta questa" }, "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. Configurazione più dettagliata disponibile nelle Impostazioni.", - "selectOptionsFirst": "Seleziona almeno un'opzione qui sotto per abilitare l'auto-approvazione", - "toggleAriaLabel": "Attiva/disattiva approvazione automatica", - "disabledAriaLabel": "Approvazione automatica disabilitata - seleziona prima le opzioni" + "title": "Approvazione automatica", + "all": "Tutti", + "none": "Nessuno", + "description": "Esegui queste azioni senza chiedere il permesso. Abilita questa opzione solo per le azioni di cui ti fidi completamente.", + "selectOptionsFirst": "Seleziona almeno un'opzione qui sotto per abilitare l'approvazione automatica", + "toggleAriaLabel": "Attiva/disattiva l'approvazione automatica", + "disabledAriaLabel": "Approvazione automatica disabilitata - seleziona prima le opzioni", + "triggerLabel_zero": "Nessuna approvazione automatica", + "triggerLabel_one": "1 approvato automaticamente", + "triggerLabel_other": "{{count}} approvati automaticamente", + "triggerLabelAll": "Approva tutto automaticamente" }, "reasoning": { "thinking": "Sto pensando", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 9a5d47fec890..f5a84fe0b95f 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -245,12 +245,17 @@ "issues": "Windows PowerShellに問題があるようです。こちらを参照してください" }, "autoApprove": { - "title": "自動承認:", + "title": "自動承認", + "all": "すべて", "none": "なし", - "description": "自動承認はRoo Codeに許可を求めずに操作を実行する権限を与えます。完全に信頼できる操作のみ有効にしてください。より詳細な設定は設定で利用できます。", + "description": "許可を求めずにこれらのアクションを実行します。完全に信頼できるアクションに対してのみ有効にしてください。", "selectOptionsFirst": "自動承認を有効にするには、以下のオプションを少なくとも1つ選択してください", "toggleAriaLabel": "自動承認の切り替え", - "disabledAriaLabel": "自動承認が無効です - 最初にオプションを選択してください" + "disabledAriaLabel": "自動承認が無効です - 最初にオプションを選択してください", + "triggerLabel_zero": "自動承認なし", + "triggerLabel_one": "1件自動承認済み", + "triggerLabel_other": "{{count}}件自動承認済み", + "triggerLabelAll": "すべて自動承認" }, "reasoning": { "thinking": "考え中", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index aaf29243b702..83515391ca28 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -245,12 +245,17 @@ "issues": "Windows PowerShell에 문제가 있는 것 같습니다. 다음을 참조하세요" }, "autoApprove": { - "title": "자동 승인:", + "title": "자동 승인", + "all": "모두", "none": "없음", - "description": "자동 승인을 사용하면 Roo Code가 권한을 요청하지 않고 작업을 수행할 수 있습니다. 완전히 신뢰할 수 있는 작업에만 활성화하세요. 더 자세한 구성은 설정에서 사용할 수 있습니다.", - "selectOptionsFirst": "자동 승인을 활성화하려면 아래 옵션 중 하나 이상을 선택하세요", - "toggleAriaLabel": "자동 승인 전환", - "disabledAriaLabel": "자동 승인 비활성화됨 - 먼저 옵션을 선택하세요" + "description": "권한을 묻지 않고 이러한 작업을 실행합니다. 완전히 신뢰하는 작업에 대해서만 활성화하십시오.", + "selectOptionsFirst": "자동 승인을 활성화하려면 아래 옵션 중 하나 이상을 선택하십시오", + "toggleAriaLabel": "자동 승인 토글", + "disabledAriaLabel": "자동 승인 비활성화됨 - 먼저 옵션을 선택하십시오", + "triggerLabel_zero": "자동 승인 없음", + "triggerLabel_one": "1개 자동 승인됨", + "triggerLabel_other": "{{count}}개 자동 승인됨", + "triggerLabelAll": "모두 자동 승인" }, "reasoning": { "thinking": "생각 중", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index c6d52fa92ed2..1417001b7ab4 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -245,12 +245,17 @@ "issues": "Het lijkt erop dat je problemen hebt met Windows PowerShell, zie deze" }, "autoApprove": { - "title": "Automatisch goedkeuren:", + "title": "Automatisch goedkeuren", + "all": "Alles", "none": "Geen", - "description": "Met automatisch goedkeuren kan Roo Code acties uitvoeren zonder om toestemming te vragen. Schakel dit alleen in voor acties die je volledig vertrouwt. Meer gedetailleerde configuratie beschikbaar in de Instellingen.", - "selectOptionsFirst": "Selecteer hieronder minstens één optie om automatische goedkeuring in te schakelen", + "description": "Voer deze acties uit zonder toestemming te vragen. Schakel dit alleen in voor acties die je volledig vertrouwt.", + "selectOptionsFirst": "Selecteer hieronder minstens één optie om automatisch goedkeuren in te schakelen", "toggleAriaLabel": "Automatisch goedkeuren in-/uitschakelen", - "disabledAriaLabel": "Automatisch goedkeuren uitgeschakeld - selecteer eerst opties" + "disabledAriaLabel": "Automatisch goedkeuren uitgeschakeld - selecteer eerst opties", + "triggerLabel_zero": "Geen automatische goedkeuring", + "triggerLabel_one": "1 automatisch goedgekeurd", + "triggerLabel_other": "{{count}} automatisch goedgekeurd", + "triggerLabelAll": "Alles automatisch goedkeuren" }, "announcement": { "title": "🎉 Roo Code {{version}} uitgebracht", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 2028cb705b34..d0cea4e8203c 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -245,12 +245,19 @@ "issues": "Wygląda na to, że masz problemy z Windows PowerShell, proszę zapoznaj się z tym" }, "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. Bardziej szczegółowa konfiguracja dostępna w Ustawieniach.", + "title": "Automatyczne zatwierdzanie", + "all": "Wszystkie", + "none": "Żadne", + "description": "Wykonuj te działania bez pytania o zgodę. Włącz tę opcję tylko dla działań, którym w pełni ufasz.", "selectOptionsFirst": "Wybierz co najmniej jedną opcję poniżej, aby włączyć automatyczne zatwierdzanie", "toggleAriaLabel": "Przełącz automatyczne zatwierdzanie", - "disabledAriaLabel": "Automatyczne zatwierdzanie wyłączone - najpierw wybierz opcje" + "disabledAriaLabel": "Automatyczne zatwierdzanie wyłączone - najpierw wybierz opcje", + "triggerLabel_zero": "Brak automatycznej akceptacji", + "triggerLabel_one": "1 automatycznie zaakceptowany", + "triggerLabel_few": "{{count}} automatycznie zaakceptowane", + "triggerLabel_many": "{{count}} automatycznie zaakceptowanych", + "triggerLabel_other": "{{count}} automatycznie zaakceptowanych", + "triggerLabelAll": "Zatwierdź wszystko automatycznie" }, "reasoning": { "thinking": "Myślenie", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 6ee23ca62746..c92146e5b5c0 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -245,12 +245,17 @@ "issues": "Parece que você está tendo problemas com o Windows PowerShell, por favor veja este" }, "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. Configuração mais detalhada disponível nas Configurações.", + "title": "Aprovação automática", + "all": "Todos", + "none": "Nenhum", + "description": "Execute estas ações sem pedir permissão. Ative isso apenas para ações em que você confia totalmente.", "selectOptionsFirst": "Selecione pelo menos uma opção abaixo para ativar a aprovação automática", "toggleAriaLabel": "Alternar aprovação automática", - "disabledAriaLabel": "Aprovação automática desativada - selecione as opções primeiro" + "disabledAriaLabel": "Aprovação automática desativada - selecione as opções primeiro", + "triggerLabel_zero": "Nenhuma aprovação automática", + "triggerLabel_one": "1 aprovado automaticamente", + "triggerLabel_other": "{{count}} aprovados automaticamente", + "triggerLabelAll": "YOLO" }, "reasoning": { "thinking": "Pensando", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 6cafe6bac991..3156768dd0b3 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -245,12 +245,19 @@ "issues": "Похоже, у вас проблемы с Windows PowerShell, пожалуйста, ознакомьтесь с этим" }, "autoApprove": { - "title": "Автоодобрение:", - "none": "Нет", - "description": "Автоодобрение позволяет Roo Code выполнять действия без запроса разрешения. Включайте только для полностью доверенных действий. Более подробная настройка доступна в Настройках.", - "selectOptionsFirst": "Выберите хотя бы один параметр ниже, чтобы включить автоодобрение", - "toggleAriaLabel": "Переключить автоодобрение", - "disabledAriaLabel": "Автоодобрение отключено - сначала выберите опции" + "title": "Авто-утверждение", + "all": "Все", + "none": "Ни одного", + "description": "Выполняйте эти действия, не спрашивая разрешения. Включайте это только для действий, которым вы полностью доверяете.", + "selectOptionsFirst": "Выберите хотя бы один вариант ниже, чтобы включить авто-утверждение", + "toggleAriaLabel": "Переключить авто-утверждение", + "disabledAriaLabel": "Авто-утверждение отключено - сначала выберите опции", + "triggerLabel_zero": "Нет авто-утверждения", + "triggerLabel_one": "1 авто-утвержден", + "triggerLabel_few": "{{count}} авто-утверждено", + "triggerLabel_many": "{{count}} авто-утверждено", + "triggerLabel_other": "{{count}} авто-утверждено", + "triggerLabelAll": "Автоматически одобрить все" }, "announcement": { "title": "🎉 Выпущен Roo Code {{version}}", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 867acfbc9fb8..f41774720e1d 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -245,12 +245,17 @@ "issues": "Windows PowerShell ile ilgili sorunlar yaşıyor gibi görünüyorsunuz, lütfen şu konuya bakın" }, "autoApprove": { - "title": "Otomatik-onay:", + "title": "Otomatik onayla", + "all": "Tümü", "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. Daha detaylı yapılandırma Ayarlar'da mevcuttur.", - "selectOptionsFirst": "Otomatik onayı etkinleştirmek için aşağıdan en az bir seçenek belirleyin", - "toggleAriaLabel": "Otomatik onayı değiştir", - "disabledAriaLabel": "Otomatik onay devre dışı - önce seçenekleri belirleyin" + "description": "İzin istemeden bu eylemleri gerçekleştirin. Bunu yalnızca tamamen güvendiğiniz eylemler için etkinleştirin.", + "selectOptionsFirst": "Otomatik onayı etkinleştirmek için aşağıdan en az bir seçenek seçin", + "toggleAriaLabel": "Otomatik onayı aç/kapat", + "disabledAriaLabel": "Otomatik onay devre dışı - önce seçenekleri seçin", + "triggerLabel_zero": "Otomatik onay yok", + "triggerLabel_one": "1 otomatik onaylandı", + "triggerLabel_other": "{{count}} otomatik onaylandı", + "triggerLabelAll": "Tümünü Otomatik Onayla" }, "reasoning": { "thinking": "Düşünüyor", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index ef8e951aace2..de43929269c2 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -245,12 +245,17 @@ "issues": "Có vẻ như bạn đang gặp vấn đề với Windows PowerShell, vui lòng xem" }, "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. Cấu hình chi tiết hơn có sẵn trong Cài đặt.", - "selectOptionsFirst": "Chọn ít nhất một tùy chọn bên dưới để bật tự động phê duyệt", - "toggleAriaLabel": "Chuyển đổi tự động phê duyệt", - "disabledAriaLabel": "Tự động phê duyệt bị vô hiệu hóa - hãy chọn các tùy chọn trước" + "title": "Tự động phê duyệt", + "all": "Tất cả", + "none": "Không có", + "description": "Thực hiện các hành động này mà không cần xin phép. Chỉ bật tính năng này cho các hành động bạn hoàn toàn tin tưởng.", + "selectOptionsFirst": "Chọn ít nhất một tùy chọn bên dưới để bật tính năng tự động phê duyệt", + "toggleAriaLabel": "Bật/tắt tự động phê duyệt", + "disabledAriaLabel": "Tự động phê duyệt đã tắt - trước tiên hãy chọn các tùy chọn", + "triggerLabel_zero": "Không có tự động phê duyệt", + "triggerLabel_one": "1 được tự động phê duyệt", + "triggerLabel_other": "{{count}} được tự động phê duyệt", + "triggerLabelAll": "Tự động phê duyệt tất cả" }, "reasoning": { "thinking": "Đang suy nghĩ", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 1e430200a1be..748924fea1a5 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -245,12 +245,17 @@ "issues": "看起来您遇到了Windows PowerShell问题,请参阅此" }, "autoApprove": { - "title": "自动批准:", + "title": "自动批准", + "all": "全部", "none": "无", - "description": "允许直接执行操作无需确认,请谨慎启用。前往设置调整", - "selectOptionsFirst": "选择至少一个下面的选项以启用自动批准", + "description": "无需请求权限即可执行这些操作。仅对您完全信任的操作启用此功能。", + "selectOptionsFirst": "请至少选择以下一个选项以启用自动批准", "toggleAriaLabel": "切换自动批准", - "disabledAriaLabel": "自动批准已禁用 - 请先选择选项" + "disabledAriaLabel": "自动批准已禁用 - 请先选择选项", + "triggerLabel_zero": "无自动批准", + "triggerLabel_one": "1 个自动批准", + "triggerLabel_other": "{{count}} 个自动批准", + "triggerLabelAll": "全部自动批准" }, "reasoning": { "thinking": "思考中", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index f5183d65a948..86ce8a08abae 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -269,12 +269,17 @@ "issues": "您似乎遇到了 Windows PowerShell 的問題,請參閱此說明文件" }, "autoApprove": { - "title": "自動核准:", + "title": "自動批准", + "all": "全部", "none": "無", - "description": "自動核准讓 Roo Code 可以在無需徵求您同意的情況下執行操作。請僅對您完全信任的動作啟用此功能。您可以在設定中進行更詳細的調整。", - "selectOptionsFirst": "請至少選擇以下一個選項以啟用自動核准", - "toggleAriaLabel": "切換自動核准", - "disabledAriaLabel": "自動核准已停用 - 請先選取選項" + "description": "無需請求權限即可執行這些操作。僅對您完全信任的操作啟用此功能。", + "selectOptionsFirst": "請至少選擇以下一個選項以啟用自動批准", + "toggleAriaLabel": "切換自動批准", + "disabledAriaLabel": "自動批准已禁用 - 請先選擇選項", + "triggerLabel_zero": "無自動核准", + "triggerLabel_one": "1 個自動核准", + "triggerLabel_other": "{{count}} 個自動核准", + "triggerLabelAll": "全部自動核准" }, "announcement": { "title": "🎉 Roo Code {{version}} 已發布", From fac84e200040811758beccffd37a6c095ed40f9b Mon Sep 17 00:00:00 2001 From: daniel-lxs Date: Thu, 11 Sep 2025 09:47:49 -0500 Subject: [PATCH 15/20] Remove dead code: deleted unused AutoApproveMenu files --- [WORKFLOW_FILE_PATH] | 1 + .../src/components/chat/AutoApproveMenu.tsx | 273 ---------------- .../chat/__tests__/AutoApproveMenu.spec.tsx | 307 ------------------ 3 files changed, 1 insertion(+), 580 deletions(-) create mode 100644 [WORKFLOW_FILE_PATH] delete mode 100644 webview-ui/src/components/chat/AutoApproveMenu.tsx delete mode 100644 webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx diff --git a/[WORKFLOW_FILE_PATH] b/[WORKFLOW_FILE_PATH] new file mode 100644 index 000000000000..80fb083f1708 --- /dev/null +++ b/[WORKFLOW_FILE_PATH] @@ -0,0 +1 @@ +[UPDATED_CONTENT_WITH_LESSON] \ No newline at end of file diff --git a/webview-ui/src/components/chat/AutoApproveMenu.tsx b/webview-ui/src/components/chat/AutoApproveMenu.tsx deleted file mode 100644 index 25b936eb7457..000000000000 --- a/webview-ui/src/components/chat/AutoApproveMenu.tsx +++ /dev/null @@ -1,273 +0,0 @@ -import { memo, useCallback, useMemo, useState } from "react" -import { Trans } from "react-i18next" -import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react" - -import { vscode } from "@src/utils/vscode" -import { useExtensionState } from "@src/context/ExtensionStateContext" -import { useAppTranslation } from "@src/i18n/TranslationContext" -import { AutoApproveToggle, AutoApproveSetting, autoApproveSettingsConfig } from "../settings/AutoApproveToggle" -import { StandardTooltip } from "@src/components/ui" -import { useAutoApprovalState } from "@src/hooks/useAutoApprovalState" -import { useAutoApprovalToggles } from "@src/hooks/useAutoApprovalToggles" -import DismissibleUpsell from "@src/components/common/DismissibleUpsell" -import { useCloudUpsell } from "@src/hooks/useCloudUpsell" -import { CloudUpsellDialog } from "@src/components/cloud/CloudUpsellDialog" - -interface AutoApproveMenuProps { - style?: React.CSSProperties -} - -const AutoApproveMenu = ({ style }: AutoApproveMenuProps) => { - const [isExpanded, setIsExpanded] = useState(false) - - const { - autoApprovalEnabled, - setAutoApprovalEnabled, - alwaysApproveResubmit, - setAlwaysAllowReadOnly, - setAlwaysAllowWrite, - setAlwaysAllowExecute, - setAlwaysAllowBrowser, - setAlwaysAllowMcp, - setAlwaysAllowModeSwitch, - setAlwaysAllowSubtasks, - setAlwaysApproveResubmit, - setAlwaysAllowFollowupQuestions, - setAlwaysAllowUpdateTodoList, - } = useExtensionState() - - const { t } = useAppTranslation() - - const { isOpen, openUpsell, closeUpsell, handleConnect } = useCloudUpsell({ - autoOpenOnAuth: false, - }) - - const baseToggles = useAutoApprovalToggles() - const enabledCount = useMemo(() => Object.values(baseToggles).filter(Boolean).length, [baseToggles]) - - // AutoApproveMenu needs alwaysApproveResubmit in addition to the base toggles - const toggles = useMemo( - () => ({ - ...baseToggles, - alwaysApproveResubmit: alwaysApproveResubmit, - }), - [baseToggles, alwaysApproveResubmit], - ) - - const { hasEnabledOptions, effectiveAutoApprovalEnabled } = useAutoApprovalState(toggles, autoApprovalEnabled) - - const onAutoApproveToggle = useCallback( - (key: AutoApproveSetting, value: boolean) => { - vscode.postMessage({ type: key, bool: value }) - - // Update the specific toggle state - switch (key) { - case "alwaysAllowReadOnly": - setAlwaysAllowReadOnly(value) - break - case "alwaysAllowWrite": - setAlwaysAllowWrite(value) - break - case "alwaysAllowExecute": - setAlwaysAllowExecute(value) - break - case "alwaysAllowBrowser": - setAlwaysAllowBrowser(value) - break - case "alwaysAllowMcp": - setAlwaysAllowMcp(value) - break - case "alwaysAllowModeSwitch": - setAlwaysAllowModeSwitch(value) - break - case "alwaysAllowSubtasks": - setAlwaysAllowSubtasks(value) - break - case "alwaysApproveResubmit": - setAlwaysApproveResubmit(value) - break - case "alwaysAllowFollowupQuestions": - setAlwaysAllowFollowupQuestions(value) - break - case "alwaysAllowUpdateTodoList": - setAlwaysAllowUpdateTodoList(value) - break - } - - // Check if we need to update the master auto-approval state - // Create a new toggles state with the updated value - const updatedToggles = { - ...toggles, - [key]: value, - } - - const willHaveEnabledOptions = Object.values(updatedToggles).some((v) => !!v) - - // If enabling the first option, enable master auto-approval - if (value && !hasEnabledOptions && willHaveEnabledOptions) { - setAutoApprovalEnabled(true) - vscode.postMessage({ type: "autoApprovalEnabled", bool: true }) - } - // If disabling the last option, disable master auto-approval - else if (!value && hasEnabledOptions && !willHaveEnabledOptions) { - setAutoApprovalEnabled(false) - vscode.postMessage({ type: "autoApprovalEnabled", bool: false }) - } - }, - [ - toggles, - hasEnabledOptions, - setAlwaysAllowReadOnly, - setAlwaysAllowWrite, - setAlwaysAllowExecute, - setAlwaysAllowBrowser, - setAlwaysAllowMcp, - setAlwaysAllowModeSwitch, - setAlwaysAllowSubtasks, - setAlwaysApproveResubmit, - setAlwaysAllowFollowupQuestions, - setAlwaysAllowUpdateTodoList, - setAutoApprovalEnabled, - ], - ) - - const toggleExpanded = useCallback(() => { - setIsExpanded((prev) => !prev) - }, []) - - const enabledActionsList = Object.entries(toggles) - .filter(([_key, value]) => !!value) - .map(([key]) => t(autoApproveSettingsConfig[key as AutoApproveSetting].labelKey)) - .join(", ") - - // Update displayed text logic - const displayText = useMemo(() => { - if (!effectiveAutoApprovalEnabled || !hasEnabledOptions) { - return t("chat:autoApprove.none") - } - return enabledActionsList || t("chat:autoApprove.none") - }, [effectiveAutoApprovalEnabled, hasEnabledOptions, enabledActionsList, t]) - - const handleOpenSettings = useCallback( - () => - window.postMessage({ type: "action", action: "settingsButtonClicked", values: { section: "autoApprove" } }), - [], - ) - - return ( -
- {isExpanded && ( -
-
- , - }} - /> -
- - - - {enabledCount > 7 && ( - <> - openUpsell()} - dismissOnClick={false} - variant="banner"> - , - }} - /> - - - )} -
- )} - -
-
e.stopPropagation()}> - - { - if (hasEnabledOptions) { - const newValue = !(autoApprovalEnabled ?? false) - setAutoApprovalEnabled(newValue) - vscode.postMessage({ type: "autoApprovalEnabled", bool: newValue }) - } - // If no options enabled, do nothing - }} - /> - -
-
- - {t("chat:autoApprove.title")} - - - {displayText} - - -
-
- -
- ) -} - -export default memo(AutoApproveMenu) diff --git a/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx b/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx deleted file mode 100644 index 185e5eeec6ea..000000000000 --- a/webview-ui/src/components/chat/__tests__/AutoApproveMenu.spec.tsx +++ /dev/null @@ -1,307 +0,0 @@ -import { render, fireEvent, screen, waitFor } from "@/utils/test-utils" -import { useExtensionState } from "@src/context/ExtensionStateContext" -import { vscode } from "@src/utils/vscode" -import AutoApproveMenu from "../AutoApproveMenu" - -// Mock vscode API -vi.mock("@src/utils/vscode", () => ({ - vscode: { - postMessage: vi.fn(), - }, -})) - -// Mock ExtensionStateContext -vi.mock("@src/context/ExtensionStateContext") - -// Mock translation hook -vi.mock("@src/i18n/TranslationContext", () => ({ - useAppTranslation: () => ({ - t: (key: string) => { - const translations: Record = { - "chat:autoApprove.title": "Auto-approve", - "chat:autoApprove.none": "None selected", - "chat:autoApprove.selectOptionsFirst": "Select at least one option below to enable auto-approval", - "chat:autoApprove.description": "Configure auto-approval settings", - "settings:autoApprove.readOnly.label": "Read-only operations", - "settings:autoApprove.write.label": "Write operations", - "settings:autoApprove.execute.label": "Execute operations", - "settings:autoApprove.browser.label": "Browser operations", - "settings:autoApprove.modeSwitch.label": "Mode switches", - "settings:autoApprove.mcp.label": "MCP operations", - "settings:autoApprove.subtasks.label": "Subtasks", - "settings:autoApprove.resubmit.label": "Resubmit", - "settings:autoApprove.followupQuestions.label": "Follow-up questions", - "settings:autoApprove.updateTodoList.label": "Update todo list", - "settings:autoApprove.apiRequestLimit.title": "API request limit", - "settings:autoApprove.apiRequestLimit.unlimited": "Unlimited", - "settings:autoApprove.apiRequestLimit.description": "Limit the number of API requests", - "settings:autoApprove.readOnly.outsideWorkspace": "Also allow outside workspace", - "settings:autoApprove.write.outsideWorkspace": "Also allow outside workspace", - "settings:autoApprove.write.delay": "Delay", - } - return translations[key] || key - }, - }), -})) - -// Get the mocked postMessage function -const mockPostMessage = vscode.postMessage as ReturnType - -describe("AutoApproveMenu", () => { - const defaultExtensionState = { - autoApprovalEnabled: true, - alwaysAllowReadOnly: false, - alwaysAllowReadOnlyOutsideWorkspace: false, - alwaysAllowWrite: false, - alwaysAllowWriteOutsideWorkspace: false, - alwaysAllowExecute: false, - alwaysAllowBrowser: false, - alwaysAllowMcp: false, - alwaysAllowModeSwitch: false, - alwaysAllowSubtasks: false, - alwaysApproveResubmit: false, - alwaysAllowFollowupQuestions: false, - alwaysAllowUpdateTodoList: false, - writeDelayMs: 3000, - allowedMaxRequests: undefined, - setAutoApprovalEnabled: vi.fn(), - setAlwaysAllowReadOnly: vi.fn(), - setAlwaysAllowWrite: vi.fn(), - setAlwaysAllowExecute: vi.fn(), - setAlwaysAllowBrowser: vi.fn(), - setAlwaysAllowMcp: vi.fn(), - setAlwaysAllowModeSwitch: vi.fn(), - setAlwaysAllowSubtasks: vi.fn(), - setAlwaysApproveResubmit: vi.fn(), - setAlwaysAllowFollowupQuestions: vi.fn(), - setAlwaysAllowUpdateTodoList: vi.fn(), - setAllowedMaxRequests: vi.fn(), - } - - beforeEach(() => { - vi.clearAllMocks() - ;(useExtensionState as ReturnType).mockReturnValue(defaultExtensionState) - }) - - describe("Master checkbox behavior", () => { - it("should show 'None selected' when no sub-options are selected", () => { - ;(useExtensionState as ReturnType).mockReturnValue({ - ...defaultExtensionState, - autoApprovalEnabled: false, - alwaysAllowReadOnly: false, - alwaysAllowWrite: false, - alwaysAllowExecute: false, - alwaysAllowBrowser: false, - alwaysAllowModeSwitch: false, - }) - - render() - - // Check that the text shows "None selected" - expect(screen.getByText("None selected")).toBeInTheDocument() - }) - - it("should show enabled options when sub-options are selected", () => { - ;(useExtensionState as ReturnType).mockReturnValue({ - ...defaultExtensionState, - autoApprovalEnabled: true, - alwaysAllowReadOnly: true, - alwaysAllowWrite: false, - }) - - render() - - // Check that the text shows the enabled option - expect(screen.getByText("Read-only operations")).toBeInTheDocument() - }) - - it("should not allow toggling master checkbox when no options are selected", () => { - ;(useExtensionState as ReturnType).mockReturnValue({ - ...defaultExtensionState, - autoApprovalEnabled: false, - alwaysAllowReadOnly: false, - }) - - render() - - // Click on the master checkbox - const masterCheckbox = screen.getByRole("checkbox") - fireEvent.click(masterCheckbox) - - // Should not send any message since no options are selected - expect(mockPostMessage).not.toHaveBeenCalled() - }) - - it("should toggle master checkbox when options are selected", () => { - ;(useExtensionState as ReturnType).mockReturnValue({ - ...defaultExtensionState, - autoApprovalEnabled: true, - alwaysAllowReadOnly: true, - }) - - render() - - // Click on the master checkbox - const masterCheckbox = screen.getByRole("checkbox") - fireEvent.click(masterCheckbox) - - // Should toggle the master checkbox - expect(mockPostMessage).toHaveBeenCalledWith({ - type: "autoApprovalEnabled", - bool: false, - }) - }) - }) - - describe("Sub-option toggles", () => { - it("should toggle read-only operations", async () => { - const mockSetAlwaysAllowReadOnly = vi.fn() - - ;(useExtensionState as ReturnType).mockReturnValue({ - ...defaultExtensionState, - setAlwaysAllowReadOnly: mockSetAlwaysAllowReadOnly, - }) - - render() - - // Expand the menu - const menuContainer = screen.getByText("Auto-approve").parentElement - fireEvent.click(menuContainer!) - - // Wait for the menu to expand and find the read-only button - await waitFor(() => { - expect(screen.getByTestId("always-allow-readonly-toggle")).toBeInTheDocument() - }) - - const readOnlyButton = screen.getByTestId("always-allow-readonly-toggle") - fireEvent.click(readOnlyButton) - - expect(mockPostMessage).toHaveBeenCalledWith({ - type: "alwaysAllowReadOnly", - bool: true, - }) - }) - - it("should toggle write operations", async () => { - const mockSetAlwaysAllowWrite = vi.fn() - - ;(useExtensionState as ReturnType).mockReturnValue({ - ...defaultExtensionState, - setAlwaysAllowWrite: mockSetAlwaysAllowWrite, - }) - - render() - - // Expand the menu - const menuContainer = screen.getByText("Auto-approve").parentElement - fireEvent.click(menuContainer!) - - await waitFor(() => { - expect(screen.getByTestId("always-allow-write-toggle")).toBeInTheDocument() - }) - - const writeButton = screen.getByTestId("always-allow-write-toggle") - fireEvent.click(writeButton) - - expect(mockPostMessage).toHaveBeenCalledWith({ - type: "alwaysAllowWrite", - bool: true, - }) - }) - }) - - describe("Complex scenarios", () => { - it("should display multiple enabled options in summary text", () => { - ;(useExtensionState as ReturnType).mockReturnValue({ - ...defaultExtensionState, - autoApprovalEnabled: true, - alwaysAllowReadOnly: true, - alwaysAllowWrite: true, - alwaysAllowExecute: true, - }) - - render() - - // Should show all enabled options in the summary - expect(screen.getByText("Read-only operations, Write operations, Execute operations")).toBeInTheDocument() - }) - - it("should handle enabling first option when none selected", async () => { - const mockSetAutoApprovalEnabled = vi.fn() - const mockSetAlwaysAllowReadOnly = vi.fn() - - ;(useExtensionState as ReturnType).mockReturnValue({ - ...defaultExtensionState, - autoApprovalEnabled: false, - alwaysAllowReadOnly: false, - setAutoApprovalEnabled: mockSetAutoApprovalEnabled, - setAlwaysAllowReadOnly: mockSetAlwaysAllowReadOnly, - }) - - render() - - // Expand the menu - const menuContainer = screen.getByText("Auto-approve").parentElement - fireEvent.click(menuContainer!) - - await waitFor(() => { - expect(screen.getByTestId("always-allow-readonly-toggle")).toBeInTheDocument() - }) - - // Enable read-only - const readOnlyButton = screen.getByTestId("always-allow-readonly-toggle") - fireEvent.click(readOnlyButton) - - // Should enable the sub-option - expect(mockPostMessage).toHaveBeenCalledWith({ - type: "alwaysAllowReadOnly", - bool: true, - }) - - // Should also enable master auto-approval - expect(mockPostMessage).toHaveBeenCalledWith({ - type: "autoApprovalEnabled", - bool: true, - }) - }) - - it("should handle disabling last option", async () => { - const mockSetAutoApprovalEnabled = vi.fn() - const mockSetAlwaysAllowReadOnly = vi.fn() - - ;(useExtensionState as ReturnType).mockReturnValue({ - ...defaultExtensionState, - autoApprovalEnabled: true, - alwaysAllowReadOnly: true, - setAutoApprovalEnabled: mockSetAutoApprovalEnabled, - setAlwaysAllowReadOnly: mockSetAlwaysAllowReadOnly, - }) - - render() - - // Expand the menu - const menuContainer = screen.getByText("Auto-approve").parentElement - fireEvent.click(menuContainer!) - - await waitFor(() => { - expect(screen.getByTestId("always-allow-readonly-toggle")).toBeInTheDocument() - }) - - // Disable read-only (the last enabled option) - const readOnlyButton = screen.getByTestId("always-allow-readonly-toggle") - fireEvent.click(readOnlyButton) - - // Should disable the sub-option - expect(mockPostMessage).toHaveBeenCalledWith({ - type: "alwaysAllowReadOnly", - bool: false, - }) - - // Should also disable master auto-approval - expect(mockPostMessage).toHaveBeenCalledWith({ - type: "autoApprovalEnabled", - bool: false, - }) - }) - }) -}) From c3642ed17c7fbaa6e063531a232a4898b73ad7dd Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Thu, 11 Sep 2025 21:02:40 +0100 Subject: [PATCH 16/20] Leans into YOLO as a label --- webview-ui/src/i18n/locales/ca/chat.json | 2 +- webview-ui/src/i18n/locales/de/chat.json | 2 +- webview-ui/src/i18n/locales/es/chat.json | 2 +- webview-ui/src/i18n/locales/fr/chat.json | 2 +- webview-ui/src/i18n/locales/hi/chat.json | 2 +- webview-ui/src/i18n/locales/id/chat.json | 2 +- webview-ui/src/i18n/locales/it/chat.json | 2 +- webview-ui/src/i18n/locales/ja/chat.json | 2 +- webview-ui/src/i18n/locales/ko/chat.json | 2 +- webview-ui/src/i18n/locales/nl/chat.json | 2 +- webview-ui/src/i18n/locales/pl/chat.json | 2 +- webview-ui/src/i18n/locales/ru/chat.json | 2 +- webview-ui/src/i18n/locales/tr/chat.json | 2 +- webview-ui/src/i18n/locales/vi/chat.json | 2 +- webview-ui/src/i18n/locales/zh-CN/chat.json | 2 +- webview-ui/src/i18n/locales/zh-TW/chat.json | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 407284fb1afe..937844ccf17e 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -255,7 +255,7 @@ "triggerLabel_zero": "Sense aprovació automàtica", "triggerLabel_one": "1 aprovació automàtica", "triggerLabel_other": "{{count}} aprovacions automàtiques", - "triggerLabelAll": "Totes les aprovacions automàtiques" + "triggerLabelAll": "YOLO" }, "reasoning": { "thinking": "Pensant", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 4a9d46559fa9..7dfbf5dc696e 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -255,7 +255,7 @@ "triggerLabel_zero": "Keine automatische Genehmigung", "triggerLabel_one": "1 automatisch genehmigt", "triggerLabel_other": "{{count}} automatisch genehmigt", - "triggerLabelAll": "Alle automatisch genehmigen" + "triggerLabelAll": "YOLO" }, "reasoning": { "thinking": "Denke nach", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 86815e685f88..c57cbbf54842 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -255,7 +255,7 @@ "triggerLabel_zero": "Sin aprobación automática", "triggerLabel_one": "1 aprobado automáticamente", "triggerLabel_other": "{{count}} aprobados automáticamente", - "triggerLabelAll": "Aprobar todo automáticamente" + "triggerLabelAll": "YOLO" }, "reasoning": { "thinking": "Pensando", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index e10e0fc220fc..8ae1fdd0fea0 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -255,7 +255,7 @@ "triggerLabel_zero": "Pas d'approbation automatique", "triggerLabel_one": "1 approuvé automatiquement", "triggerLabel_other": "{{count}} approuvés automatiquement", - "triggerLabelAll": "Tout approuver automatiquement" + "triggerLabelAll": "YOLO" }, "reasoning": { "thinking": "Réflexion", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 873403ef0fdf..e1560fa1eece 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -255,7 +255,7 @@ "triggerLabel_zero": "कोई स्वतः-अनुमोदन नहीं", "triggerLabel_one": "1 स्वतः-अनुमोदित", "triggerLabel_other": "{{count}} स्वतः-अनुमोदित", - "triggerLabelAll": "सभी को स्वतः-स्वीकृत करें" + "triggerLabelAll": "योलो" }, "reasoning": { "thinking": "विचार कर रहा है", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 1c593b013aac..4434fdb51fa1 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -282,7 +282,7 @@ "triggerLabel_zero": "Tidak ada persetujuan otomatis", "triggerLabel_one": "1 disetujui otomatis", "triggerLabel_other": "{{count}} disetujui otomatis", - "triggerLabelAll": "Setujui Semua Secara Otomatis" + "triggerLabelAll": "YOLO" }, "announcement": { "title": "🎉 Roo Code {{version}} Dirilis", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index a101a72bd03e..76c0ff4625de 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -255,7 +255,7 @@ "triggerLabel_zero": "Nessuna approvazione automatica", "triggerLabel_one": "1 approvato automaticamente", "triggerLabel_other": "{{count}} approvati automaticamente", - "triggerLabelAll": "Approva tutto automaticamente" + "triggerLabelAll": "YOLO" }, "reasoning": { "thinking": "Sto pensando", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index f5a84fe0b95f..20c46683bb8e 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -255,7 +255,7 @@ "triggerLabel_zero": "自動承認なし", "triggerLabel_one": "1件自動承認済み", "triggerLabel_other": "{{count}}件自動承認済み", - "triggerLabelAll": "すべて自動承認" + "triggerLabelAll": "YOLO" }, "reasoning": { "thinking": "考え中", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 83515391ca28..b01424639951 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -255,7 +255,7 @@ "triggerLabel_zero": "자동 승인 없음", "triggerLabel_one": "1개 자동 승인됨", "triggerLabel_other": "{{count}}개 자동 승인됨", - "triggerLabelAll": "모두 자동 승인" + "triggerLabelAll": "욜로" }, "reasoning": { "thinking": "생각 중", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 1417001b7ab4..0519c5715c41 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -255,7 +255,7 @@ "triggerLabel_zero": "Geen automatische goedkeuring", "triggerLabel_one": "1 automatisch goedgekeurd", "triggerLabel_other": "{{count}} automatisch goedgekeurd", - "triggerLabelAll": "Alles automatisch goedkeuren" + "triggerLabelAll": "YOLO" }, "announcement": { "title": "🎉 Roo Code {{version}} uitgebracht", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index d0cea4e8203c..5baacbffdc9d 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -257,7 +257,7 @@ "triggerLabel_few": "{{count}} automatycznie zaakceptowane", "triggerLabel_many": "{{count}} automatycznie zaakceptowanych", "triggerLabel_other": "{{count}} automatycznie zaakceptowanych", - "triggerLabelAll": "Zatwierdź wszystko automatycznie" + "triggerLabelAll": "YOLO" }, "reasoning": { "thinking": "Myślenie", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 3156768dd0b3..d10ed5e9fe7c 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -257,7 +257,7 @@ "triggerLabel_few": "{{count}} авто-утверждено", "triggerLabel_many": "{{count}} авто-утверждено", "triggerLabel_other": "{{count}} авто-утверждено", - "triggerLabelAll": "Автоматически одобрить все" + "triggerLabelAll": "YOLO" }, "announcement": { "title": "🎉 Выпущен Roo Code {{version}}", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index f41774720e1d..d6f089469456 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -255,7 +255,7 @@ "triggerLabel_zero": "Otomatik onay yok", "triggerLabel_one": "1 otomatik onaylandı", "triggerLabel_other": "{{count}} otomatik onaylandı", - "triggerLabelAll": "Tümünü Otomatik Onayla" + "triggerLabelAll": "YOLO" }, "reasoning": { "thinking": "Düşünüyor", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index de43929269c2..feb1f8172223 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -255,7 +255,7 @@ "triggerLabel_zero": "Không có tự động phê duyệt", "triggerLabel_one": "1 được tự động phê duyệt", "triggerLabel_other": "{{count}} được tự động phê duyệt", - "triggerLabelAll": "Tự động phê duyệt tất cả" + "triggerLabelAll": "YOLO" }, "reasoning": { "thinking": "Đang suy nghĩ", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 748924fea1a5..ca4ce0eb1680 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -255,7 +255,7 @@ "triggerLabel_zero": "无自动批准", "triggerLabel_one": "1 个自动批准", "triggerLabel_other": "{{count}} 个自动批准", - "triggerLabelAll": "全部自动批准" + "triggerLabelAll": "人生只有一次" }, "reasoning": { "thinking": "思考中", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 86ce8a08abae..23870e96bf94 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -279,7 +279,7 @@ "triggerLabel_zero": "無自動核准", "triggerLabel_one": "1 個自動核准", "triggerLabel_other": "{{count}} 個自動核准", - "triggerLabelAll": "全部自動核准" + "triggerLabelAll": "人生只有一次" }, "announcement": { "title": "🎉 Roo Code {{version}} 已發布", From bbca0c46320b69506d6d925ff272a6c55eb1704a Mon Sep 17 00:00:00 2001 From: Bruno Bergher Date: Thu, 11 Sep 2025 21:03:01 +0100 Subject: [PATCH 17/20] Fixes broken behavior of the dropdowns in narrow windows --- .../src/components/chat/ApiConfigSelector.tsx | 2 +- .../src/components/chat/ChatTextArea.tsx | 54 +++++++++---------- 2 files changed, 25 insertions(+), 31 deletions(-) diff --git a/webview-ui/src/components/chat/ApiConfigSelector.tsx b/webview-ui/src/components/chat/ApiConfigSelector.tsx index 6d3f458b347c..653bfc39e54b 100644 --- a/webview-ui/src/components/chat/ApiConfigSelector.tsx +++ b/webview-ui/src/components/chat/ApiConfigSelector.tsx @@ -149,7 +149,7 @@ export const ApiConfigSelector = ({ disabled={disabled} data-testid="dropdown-trigger" className={cn( - "w-full min-w-0 max-w-full inline-flex items-center gap-1.5 relative whitespace-nowrap px-1.5 py-1 text-xs", + "min-w-0 inline-flex items-center gap-1.5 relative whitespace-nowrap px-1.5 py-1 text-xs", "bg-transparent border border-[rgba(255,255,255,0.08)] rounded-md text-vscode-foreground", "transition-all duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-vscode-focusBorder focus-visible:ring-inset", disabled diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index c163199a733d..b93a6c2a6ba0 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -1174,37 +1174,31 @@ export const ChatTextArea = forwardRef( /> )} -
-
-
- -
-
- -
-
- -
+
+
+ + +
-
+
{isTtsPlaying && ( - - - - + +
diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 937844ccf17e..3c26fc825e63 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -245,6 +245,7 @@ "issues": "Sembla que estàs tenint problemes amb Windows PowerShell, si us plau consulta aquesta documentació per a més informació." }, "autoApprove": { + "tooltip": "Gestiona la configuració d'aprovació automàtica", "title": "Aprovació automàtica", "all": "Totes", "none": "Cap", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 7dfbf5dc696e..bdbd5f32b6a7 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -245,6 +245,7 @@ "issues": "Es scheint, dass du Probleme mit Windows PowerShell hast, bitte sieh dir dies an" }, "autoApprove": { + "tooltip": "Einstellungen für die automatische Genehmigung verwalten", "title": "Automatische Genehmigung", "all": "Alle", "none": "Keine", diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 6fc6e5ce1b0f..d898a98bf06d 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -269,6 +269,7 @@ "issues": "It seems like you're having Windows PowerShell issues, please see this" }, "autoApprove": { + "tooltip": "Manage auto-approval settings", "title": "Auto-approve", "all": "All", "none": "None", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index c57cbbf54842..a580d30251bc 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -245,6 +245,7 @@ "issues": "Parece que estás teniendo problemas con Windows PowerShell, por favor consulta esta" }, "autoApprove": { + "tooltip": "Gestionar la configuración de aprobación automática", "title": "Aprobación automática", "all": "Todo", "none": "Ninguno", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 8ae1fdd0fea0..13120c3c6f6a 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -245,6 +245,7 @@ "issues": "Il semble que vous rencontriez des problèmes avec Windows PowerShell, veuillez consulter ce" }, "autoApprove": { + "tooltip": "Gérer les paramètres d'approbation automatique", "title": "Approbation automatique", "all": "Tout", "none": "Aucun", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index e1560fa1eece..c23c841a8e45 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -245,6 +245,7 @@ "issues": "ऐसा लगता है कि आपको Windows PowerShell के साथ समस्याएँ हो रही हैं, कृपया इसे देखें" }, "autoApprove": { + "tooltip": "स्वतः-अनुमोदन सेटिंग्स प्रबंधित करें", "title": "स्वतः-अनुमोदन", "all": "सभी", "none": "कोई नहीं", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 4434fdb51fa1..d5b63c4b7114 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -272,6 +272,7 @@ "issues": "Sepertinya kamu mengalami masalah Windows PowerShell, silakan lihat ini" }, "autoApprove": { + "tooltip": "Kelola pengaturan persetujuan otomatis", "title": "Persetujuan otomatis", "all": "Semua", "none": "Tidak Ada", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 76c0ff4625de..6c8ce6736556 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -245,6 +245,7 @@ "issues": "Sembra che tu stia avendo problemi con Windows PowerShell, consulta questa" }, "autoApprove": { + "tooltip": "Gestisci le impostazioni di approvazione automatica", "title": "Approvazione automatica", "all": "Tutti", "none": "Nessuno", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 20c46683bb8e..e706d32d08b5 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -245,6 +245,7 @@ "issues": "Windows PowerShellに問題があるようです。こちらを参照してください" }, "autoApprove": { + "tooltip": "自動承認設定を管理", "title": "自動承認", "all": "すべて", "none": "なし", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index b01424639951..770657d0d3a5 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -245,6 +245,7 @@ "issues": "Windows PowerShell에 문제가 있는 것 같습니다. 다음을 참조하세요" }, "autoApprove": { + "tooltip": "자동 승인 설정 관리", "title": "자동 승인", "all": "모두", "none": "없음", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 0519c5715c41..653d727e29bf 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -245,6 +245,7 @@ "issues": "Het lijkt erop dat je problemen hebt met Windows PowerShell, zie deze" }, "autoApprove": { + "tooltip": "Beheer instellingen voor automatische goedkeuring", "title": "Automatisch goedkeuren", "all": "Alles", "none": "Geen", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 5baacbffdc9d..33920aac5656 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -245,6 +245,7 @@ "issues": "Wygląda na to, że masz problemy z Windows PowerShell, proszę zapoznaj się z tym" }, "autoApprove": { + "tooltip": "Zarządzaj ustawieniami automatycznego zatwierdzania", "title": "Automatyczne zatwierdzanie", "all": "Wszystkie", "none": "Żadne", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index c92146e5b5c0..bf9c0bde5784 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -245,6 +245,7 @@ "issues": "Parece que você está tendo problemas com o Windows PowerShell, por favor veja este" }, "autoApprove": { + "tooltip": "Gerenciar configurações de aprovação automática", "title": "Aprovação automática", "all": "Todos", "none": "Nenhum", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index d10ed5e9fe7c..7ceb0eb35e56 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -245,6 +245,7 @@ "issues": "Похоже, у вас проблемы с Windows PowerShell, пожалуйста, ознакомьтесь с этим" }, "autoApprove": { + "tooltip": "Управление настройками автоматического одобрения", "title": "Авто-утверждение", "all": "Все", "none": "Ни одного", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index d6f089469456..cb1a22d5f476 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -245,6 +245,7 @@ "issues": "Windows PowerShell ile ilgili sorunlar yaşıyor gibi görünüyorsunuz, lütfen şu konuya bakın" }, "autoApprove": { + "tooltip": "Otomatik onay ayarlarını yönet", "title": "Otomatik onayla", "all": "Tümü", "none": "Hiçbiri", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index feb1f8172223..1dc4d2583ddb 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -245,6 +245,7 @@ "issues": "Có vẻ như bạn đang gặp vấn đề với Windows PowerShell, vui lòng xem" }, "autoApprove": { + "tooltip": "Quản lý cài đặt tự động phê duyệt", "title": "Tự động phê duyệt", "all": "Tất cả", "none": "Không có", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index ca4ce0eb1680..ef48ee4d041a 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -245,6 +245,7 @@ "issues": "看起来您遇到了Windows PowerShell问题,请参阅此" }, "autoApprove": { + "tooltip": "管理自动批准设置", "title": "自动批准", "all": "全部", "none": "无", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 23870e96bf94..c933acb0680c 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -269,6 +269,7 @@ "issues": "您似乎遇到了 Windows PowerShell 的問題,請參閱此說明文件" }, "autoApprove": { + "tooltip": "管理自動批准設定", "title": "自動批准", "all": "全部", "none": "無", From 0df4434dfe3d35997313dec85ee8d4fa9473d199 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Sun, 14 Sep 2025 10:19:17 -0400 Subject: [PATCH 20/20] Delete [WORKFLOW_FILE_PATH] --- [WORKFLOW_FILE_PATH] | 1 - 1 file changed, 1 deletion(-) delete mode 100644 [WORKFLOW_FILE_PATH] diff --git a/[WORKFLOW_FILE_PATH] b/[WORKFLOW_FILE_PATH] deleted file mode 100644 index 80fb083f1708..000000000000 --- a/[WORKFLOW_FILE_PATH] +++ /dev/null @@ -1 +0,0 @@ -[UPDATED_CONTENT_WITH_LESSON] \ No newline at end of file