From 056465f456085e7c46f4af42bada1ff9aa8e18a7 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Thu, 5 Feb 2026 22:14:10 -0700 Subject: [PATCH 1/8] feat: group consecutive list_files tool calls into single UI block Consolidate consecutive listFilesTopLevel/listFilesRecursive ask messages into a single 'Roo wants to view multiple directories' block, matching the existing read_file batching pattern. --- packages/types/src/vscode-extension-host.ts | 6 ++ .../chat/BatchListFilesPermission.tsx | 47 ++++++++++ webview-ui/src/components/chat/ChatRow.tsx | 41 ++++++++- webview-ui/src/components/chat/ChatView.tsx | 74 +++++++++++++++- .../BatchListFilesPermission.spec.tsx | 87 +++++++++++++++++++ webview-ui/src/i18n/locales/en/chat.json | 2 + 6 files changed, 253 insertions(+), 4 deletions(-) create mode 100644 webview-ui/src/components/chat/BatchListFilesPermission.tsx create mode 100644 webview-ui/src/components/chat/__tests__/BatchListFilesPermission.spec.tsx diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index fa2f04c0e5d..a0aa6f2e520 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -845,6 +845,12 @@ export interface ClineSayTool { startLine?: number }> }> + batchDirs?: Array<{ + path: string + recursive: boolean + isOutsideWorkspace?: boolean + key: string + }> question?: string imageData?: string // Base64 encoded image data for generated images // Properties for runSlashCommand tool diff --git a/webview-ui/src/components/chat/BatchListFilesPermission.tsx b/webview-ui/src/components/chat/BatchListFilesPermission.tsx new file mode 100644 index 00000000000..a1d909a5240 --- /dev/null +++ b/webview-ui/src/components/chat/BatchListFilesPermission.tsx @@ -0,0 +1,47 @@ +import { memo } from "react" + +import { ToolUseBlock, ToolUseBlockHeader } from "../common/ToolUseBlock" +import { PathTooltip } from "../ui/PathTooltip" + +interface DirPermissionItem { + path: string + recursive: boolean + isOutsideWorkspace?: boolean + key: string +} + +interface BatchListFilesPermissionProps { + dirs: DirPermissionItem[] + ts: number +} + +export const BatchListFilesPermission = memo(({ dirs = [], ts }: BatchListFilesPermissionProps) => { + if (!dirs?.length) { + return null + } + + return ( +
+
+ {dirs.map((dir) => { + return ( +
+ + + + + {dir.path} + + +
+
+
+
+ ) + })} +
+
+ ) +}) + +BatchListFilesPermission.displayName = "BatchListFilesPermission" diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 74639ff5ac5..b01b3a2775f 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -40,6 +40,7 @@ import { Mention } from "./Mention" import { CheckpointSaved } from "./checkpoints/CheckpointSaved" import { FollowUpSuggest } from "./FollowUpSuggest" import { BatchFilePermission } from "./BatchFilePermission" +import { BatchListFilesPermission } from "./BatchListFilesPermission" import { BatchDiffApproval } from "./BatchDiffApproval" import { ProgressIndicator } from "./ProgressIndicator" import { Markdown } from "./Markdown" @@ -741,7 +742,24 @@ export const ChatRowContent = ({ ) } - case "listFilesTopLevel": + case "listFilesTopLevel": { + // Check if this is a batch directory listing request + const isBatchDirRequest = message.type === "ask" && tool.batchDirs && Array.isArray(tool.batchDirs) + + if (isBatchDirRequest) { + return ( + <> +
+ + + {t("chat:directoryOperations.wantsToViewMultipleDirectories")} + +
+ + + ) + } + return ( <>
@@ -767,7 +785,25 @@ export const ChatRowContent = ({
) - case "listFilesRecursive": + } + case "listFilesRecursive": { + // Check if this is a batch directory listing request + const isBatchDirRequest = message.type === "ask" && tool.batchDirs && Array.isArray(tool.batchDirs) + + if (isBatchDirRequest) { + return ( + <> +
+ + + {t("chat:directoryOperations.wantsToViewMultipleDirectories")} + +
+ + + ) + } + return ( <>
@@ -793,6 +829,7 @@ export const ChatRowContent = ({
) + } case "searchFiles": return ( <> diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 5c377eb1f59..9920ce15a98 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -1154,8 +1154,21 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + if (msg.type !== "ask" || msg.ask !== "tool") return false + try { + const tool = JSON.parse(msg.text || "{}") + return ( + (tool.tool === "listFilesTopLevel" || tool.tool === "listFilesRecursive") && !tool.batchDirs // Don't re-batch already batched + ) + } catch { + return false + } + } + // Consolidate consecutive read_file ask messages into batches - const result: ClineMessage[] = [] + const readFileBatched: ClineMessage[] = [] let i = 0 while (i < filtered.length) { const msg = filtered[i] @@ -1199,10 +1212,67 @@ const ChatViewComponent: React.ForwardRefRenderFunction 1) { + // Create a synthetic batch message + const batchDirs = batch.map((batchMsg) => { + try { + const tool = JSON.parse(batchMsg.text || "{}") + return { + path: tool.path || "", + recursive: tool.tool === "listFilesRecursive", + isOutsideWorkspace: tool.isOutsideWorkspace || false, + key: tool.path || "", + } + } catch { + return { path: "", recursive: false, key: "" } + } + }) + + // Use the first message as the base, but add batchDirs + const firstTool = JSON.parse(msg.text || "{}") + const syntheticMessage: ClineMessage = { + ...msg, + text: JSON.stringify({ + ...firstTool, + batchDirs, + }), + // Store original messages for response handling + _batchedMessages: batch, + } as ClineMessage & { _batchedMessages: ClineMessage[] } + + result.push(syntheticMessage) + i = j // Skip past all batched messages + } else { + // Single list_files ask, keep as-is result.push(msg) i++ } diff --git a/webview-ui/src/components/chat/__tests__/BatchListFilesPermission.spec.tsx b/webview-ui/src/components/chat/__tests__/BatchListFilesPermission.spec.tsx new file mode 100644 index 00000000000..d26205e3fc4 --- /dev/null +++ b/webview-ui/src/components/chat/__tests__/BatchListFilesPermission.spec.tsx @@ -0,0 +1,87 @@ +import { render, screen } from "@/utils/test-utils" + +import { TranslationProvider } from "@/i18n/__mocks__/TranslationContext" + +import { BatchListFilesPermission } from "../BatchListFilesPermission" + +describe("BatchListFilesPermission", () => { + const mockDirs = [ + { + key: "apps/cli", + path: "apps/cli", + recursive: false, + isOutsideWorkspace: false, + }, + { + key: "apps/web-roo-code", + path: "apps/web-roo-code", + recursive: false, + isOutsideWorkspace: false, + }, + { + key: "packages/core", + path: "packages/core", + recursive: true, + isOutsideWorkspace: false, + }, + ] + + beforeEach(() => { + vi.clearAllMocks() + }) + + it("renders directory list correctly", () => { + render( + + + , + ) + + expect(screen.getByText("apps/cli")).toBeInTheDocument() + expect(screen.getByText("apps/web-roo-code")).toBeInTheDocument() + expect(screen.getByText("packages/core")).toBeInTheDocument() + }) + + it("renders nothing when dirs array is empty", () => { + const { container } = render( + + + , + ) + + expect(container.firstChild).toBeNull() + }) + + it("re-renders when timestamp changes", () => { + const { rerender } = render( + + + , + ) + + expect(screen.getByText("apps/cli")).toBeInTheDocument() + + rerender( + + + , + ) + + expect(screen.getByText("apps/cli")).toBeInTheDocument() + }) + + it("renders all directories in a single container", () => { + render( + + + , + ) + + // All directories should be within a single bordered container + const container = screen.getByText("apps/cli").closest(".border.border-border.rounded-md") + expect(container).toBeInTheDocument() + + // All 3 dirs should be inside this container + expect(container?.querySelectorAll(".flex.items-center.gap-2")).toHaveLength(mockDirs.length) + }) +}) diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index b9652cfce5c..b60ad596208 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -235,6 +235,8 @@ "didViewRecursive": "Roo recursively viewed all files in this directory", "wantsToViewRecursiveOutsideWorkspace": "Roo wants to recursively view all files in this directory (outside workspace)", "didViewRecursiveOutsideWorkspace": "Roo recursively viewed all files in this directory (outside workspace)", + "wantsToViewMultipleDirectories": "Roo wants to view multiple directories", + "didViewMultipleDirectories": "Roo viewed multiple directories", "wantsToSearch": "Roo wants to search this directory for {{regex}}", "didSearch": "Roo searched this directory for {{regex}}", "wantsToSearchOutsideWorkspace": "Roo wants to search this directory (outside workspace) for {{regex}}", From 01608474ee98571febfcbe7294b6519e82ecc55a Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Thu, 5 Feb 2026 22:19:56 -0700 Subject: [PATCH 2/8] chore: add missing translation keys for all locales --- webview-ui/src/i18n/locales/ca/chat.json | 4 +++- webview-ui/src/i18n/locales/de/chat.json | 4 +++- webview-ui/src/i18n/locales/es/chat.json | 4 +++- webview-ui/src/i18n/locales/fr/chat.json | 4 +++- webview-ui/src/i18n/locales/hi/chat.json | 4 +++- webview-ui/src/i18n/locales/id/chat.json | 4 +++- webview-ui/src/i18n/locales/it/chat.json | 4 +++- webview-ui/src/i18n/locales/ja/chat.json | 4 +++- webview-ui/src/i18n/locales/ko/chat.json | 4 +++- webview-ui/src/i18n/locales/nl/chat.json | 8 +++++--- webview-ui/src/i18n/locales/pl/chat.json | 8 +++++--- webview-ui/src/i18n/locales/pt-BR/chat.json | 4 +++- webview-ui/src/i18n/locales/ru/chat.json | 4 +++- webview-ui/src/i18n/locales/tr/chat.json | 4 +++- webview-ui/src/i18n/locales/vi/chat.json | 4 +++- webview-ui/src/i18n/locales/zh-CN/chat.json | 4 +++- webview-ui/src/i18n/locales/zh-TW/chat.json | 8 +++++--- 17 files changed, 57 insertions(+), 23 deletions(-) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 5bba7dc4459..bb9eb344787 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo vol veure els fitxers de nivell superior en aquest directori (fora de l'espai de treball)", "didViewTopLevelOutsideWorkspace": "Roo ha vist els fitxers de nivell superior en aquest directori (fora de l'espai de treball)", "wantsToViewRecursiveOutsideWorkspace": "Roo vol veure recursivament tots els fitxers en aquest directori (fora de l'espai de treball)", - "didViewRecursiveOutsideWorkspace": "Roo ha vist recursivament tots els fitxers en aquest directori (fora de l'espai de treball)" + "didViewRecursiveOutsideWorkspace": "Roo ha vist recursivament tots els fitxers en aquest directori (fora de l'espai de treball)", + "wantsToViewMultipleDirectories": "Roo vol veure diversos directoris", + "didViewMultipleDirectories": "Roo ha vist diversos directoris" }, "commandOutput": "Sortida de la comanda", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 58bc85b60cf..054c8b5e038 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo möchte die Dateien auf oberster Ebene in diesem Verzeichnis (außerhalb des Arbeitsbereichs) anzeigen", "didViewTopLevelOutsideWorkspace": "Roo hat die Dateien auf oberster Ebene in diesem Verzeichnis (außerhalb des Arbeitsbereichs) angezeigt", "wantsToViewRecursiveOutsideWorkspace": "Roo möchte rekursiv alle Dateien in diesem Verzeichnis (außerhalb des Arbeitsbereichs) anzeigen", - "didViewRecursiveOutsideWorkspace": "Roo hat rekursiv alle Dateien in diesem Verzeichnis (außerhalb des Arbeitsbereichs) angezeigt" + "didViewRecursiveOutsideWorkspace": "Roo hat rekursiv alle Dateien in diesem Verzeichnis (außerhalb des Arbeitsbereichs) angezeigt", + "wantsToViewMultipleDirectories": "Roo möchte mehrere Verzeichnisse anzeigen", + "didViewMultipleDirectories": "Roo hat mehrere Verzeichnisse angezeigt" }, "commandOutput": "Befehlsausgabe", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index 6c894642c88..d4e42724550 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo quiere ver los archivos de nivel superior en este directorio (fuera del espacio de trabajo)", "didViewTopLevelOutsideWorkspace": "Roo vio los archivos de nivel superior en este directorio (fuera del espacio de trabajo)", "wantsToViewRecursiveOutsideWorkspace": "Roo quiere ver recursivamente todos los archivos en este directorio (fuera del espacio de trabajo)", - "didViewRecursiveOutsideWorkspace": "Roo vio recursivamente todos los archivos en este directorio (fuera del espacio de trabajo)" + "didViewRecursiveOutsideWorkspace": "Roo vio recursivamente todos los archivos en este directorio (fuera del espacio de trabajo)", + "wantsToViewMultipleDirectories": "Roo quiere ver varios directorios", + "didViewMultipleDirectories": "Roo vio varios directorios" }, "commandOutput": "Salida del comando", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 598344de1d7..95dc15a67b0 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo veut voir les fichiers de premier niveau dans ce répertoire (hors espace de travail)", "didViewTopLevelOutsideWorkspace": "Roo a vu les fichiers de premier niveau dans ce répertoire (hors espace de travail)", "wantsToViewRecursiveOutsideWorkspace": "Roo veut voir récursivement tous les fichiers dans ce répertoire (hors espace de travail)", - "didViewRecursiveOutsideWorkspace": "Roo a vu récursivement tous les fichiers dans ce répertoire (hors espace de travail)" + "didViewRecursiveOutsideWorkspace": "Roo a vu récursivement tous les fichiers dans ce répertoire (hors espace de travail)", + "wantsToViewMultipleDirectories": "Roo veut voir plusieurs répertoires", + "didViewMultipleDirectories": "Roo a vu plusieurs répertoires" }, "commandOutput": "Sortie de la Commande", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index 74d5e4e3eef..cf385e7dafa 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo इस निर्देशिका (कार्यक्षेत्र के बाहर) में शीर्ष स्तर की फ़ाइलें देखना चाहता है", "didViewTopLevelOutsideWorkspace": "Roo ने इस निर्देशिका (कार्यक्षेत्र के बाहर) में शीर्ष स्तर की फ़ाइलें देखीं", "wantsToViewRecursiveOutsideWorkspace": "Roo इस निर्देशिका (कार्यक्षेत्र के बाहर) में सभी फ़ाइलों को पुनरावर्ती रूप से देखना चाहता है", - "didViewRecursiveOutsideWorkspace": "Roo ने इस निर्देशिका (कार्यक्षेत्र के बाहर) में सभी फ़ाइलों को पुनरावर्ती रूप से देखा" + "didViewRecursiveOutsideWorkspace": "Roo ने इस निर्देशिका (कार्यक्षेत्र के बाहर) में सभी फ़ाइलों को पुनरावर्ती रूप से देखा", + "wantsToViewMultipleDirectories": "Roo कई डायरेक्ट्रीज़ देखना चाहता है", + "didViewMultipleDirectories": "Roo ने कई डायरेक्ट्रीज़ देखीं" }, "commandOutput": "कमांड आउटपुट", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index f814b9d4a9a..66ef4b30fec 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -246,7 +246,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo ingin melihat file tingkat atas di direktori ini (di luar workspace)", "didViewTopLevelOutsideWorkspace": "Roo melihat file tingkat atas di direktori ini (di luar workspace)", "wantsToViewRecursiveOutsideWorkspace": "Roo ingin melihat semua file secara rekursif di direktori ini (di luar workspace)", - "didViewRecursiveOutsideWorkspace": "Roo melihat semua file secara rekursif di direktori ini (di luar workspace)" + "didViewRecursiveOutsideWorkspace": "Roo melihat semua file secara rekursif di direktori ini (di luar workspace)", + "wantsToViewMultipleDirectories": "Roo ingin melihat beberapa direktori", + "didViewMultipleDirectories": "Roo melihat beberapa direktori" }, "codebaseSearch": { "wantsToSearch": "Roo ingin mencari codebase untuk {{query}}", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index eca4264df20..b2078f37e9e 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo vuole visualizzare i file di primo livello in questa directory (fuori dall'area di lavoro)", "didViewTopLevelOutsideWorkspace": "Roo ha visualizzato i file di primo livello in questa directory (fuori dall'area di lavoro)", "wantsToViewRecursiveOutsideWorkspace": "Roo vuole visualizzare ricorsivamente tutti i file in questa directory (fuori dall'area di lavoro)", - "didViewRecursiveOutsideWorkspace": "Roo ha visualizzato ricorsivamente tutti i file in questa directory (fuori dall'area di lavoro)" + "didViewRecursiveOutsideWorkspace": "Roo ha visualizzato ricorsivamente tutti i file in questa directory (fuori dall'area di lavoro)", + "wantsToViewMultipleDirectories": "Roo vuole visualizzare più directory", + "didViewMultipleDirectories": "Roo ha visualizzato più directory" }, "commandOutput": "Output del Comando", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 9a0b1b2a35c..eee2b888fc3 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Rooはこのディレクトリ(ワークスペース外)のトップレベルファイルを表示したい", "didViewTopLevelOutsideWorkspace": "Rooはこのディレクトリ(ワークスペース外)のトップレベルファイルを表示しました", "wantsToViewRecursiveOutsideWorkspace": "Rooはこのディレクトリ(ワークスペース外)のすべてのファイルを再帰的に表示したい", - "didViewRecursiveOutsideWorkspace": "Rooはこのディレクトリ(ワークスペース外)のすべてのファイルを再帰的に表示しました" + "didViewRecursiveOutsideWorkspace": "Rooはこのディレクトリ(ワークスペース外)のすべてのファイルを再帰的に表示しました", + "wantsToViewMultipleDirectories": "Roo は複数のディレクトリを表示したい", + "didViewMultipleDirectories": "Roo は複数のディレクトリを表示しました" }, "commandOutput": "コマンド出力", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 3b26c6e6e19..1227dd6c15e 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo가 이 디렉토리(워크스페이스 외부)의 최상위 파일을 보고 싶어합니다", "didViewTopLevelOutsideWorkspace": "Roo가 이 디렉토리(워크스페이스 외부)의 최상위 파일을 보았습니다", "wantsToViewRecursiveOutsideWorkspace": "Roo가 이 디렉토리(워크스페이스 외부)의 모든 파일을 재귀적으로 보고 싶어합니다", - "didViewRecursiveOutsideWorkspace": "Roo가 이 디렉토리(워크스페이스 외부)의 모든 파일을 재귀적으로 보았습니다" + "didViewRecursiveOutsideWorkspace": "Roo가 이 디렉토리(워크스페이스 외부)의 모든 파일을 재귀적으로 보았습니다", + "wantsToViewMultipleDirectories": "Roo가 여러 디렉토리를 보려고 합니다", + "didViewMultipleDirectories": "Roo가 여러 디렉토리를 확인했습니다" }, "commandOutput": "명령 출력", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 241e9f22166..c6391805253 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -145,14 +145,14 @@ "rateLimitWait": "Snelheidsbeperking", "errorTitle": "Fout van provider {{code}}", "errorMessage": { - "docs": "Documentatie", - "goToSettings": "Instellingen", "400": "De provider kon het verzoek niet verwerken zoals ingediend. Stop de taak en probeer een ander benadering.", "401": "Kon niet authenticeren met provider. Controleer je API-sleutelconfiguratie.", "402": "Het lijkt erop dat je funds/credits op je account op zijn. Ga naar je provider en voeg meer toe om door te gaan.", "403": "Niet geautoriseerd. Je API-sleutel is geldig, maar de provider weigerde dit verzoek in te willigen.", "429": "Te veel verzoeken. Je bent rate-gelimiteerd door de provider. Wacht alsjeblieft even voor je volgende API-aanroep.", "500": "Provider-serverfout. Er is iets mis aan de kant van de provider, er is niets mis met je verzoek.", + "docs": "Documentatie", + "goToSettings": "Instellingen", "unknown": "Onbekende API-fout. Neem alsjeblieft contact op met Roo Code-ondersteuning.", "connection": "Verbindingsfout. Zorg ervoor dat je een werkende internetverbinding hebt.", "claudeCodeNotAuthenticated": "Je moet inloggen om Claude Code te gebruiken. Ga naar Instellingen en klik op \"Inloggen bij Claude Code\" om te authenticeren." @@ -213,7 +213,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo wil de bovenliggende bestanden in deze map (buiten werkruimte) bekijken", "didViewTopLevelOutsideWorkspace": "Roo heeft de bovenliggende bestanden in deze map (buiten werkruimte) bekeken", "wantsToViewRecursiveOutsideWorkspace": "Roo wil alle bestanden in deze map (buiten werkruimte) recursief bekijken", - "didViewRecursiveOutsideWorkspace": "Roo heeft alle bestanden in deze map (buiten werkruimte) recursief bekeken" + "didViewRecursiveOutsideWorkspace": "Roo heeft alle bestanden in deze map (buiten werkruimte) recursief bekeken", + "wantsToViewMultipleDirectories": "Roo wil meerdere mappen bekijken", + "didViewMultipleDirectories": "Roo heeft meerdere mappen bekeken" }, "commandOutput": "Commando-uitvoer", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index ae6b5a96ac7..74711695532 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -150,14 +150,14 @@ "rateLimitWait": "Ograniczenie szybkości", "errorTitle": "Błąd dostawcy {{code}}", "errorMessage": { - "docs": "Dokumentacja", - "goToSettings": "Ustawienia", "400": "Dostawca nie mógł przetworzyć żądania. Zatrzymaj zadanie i spróbuj innego podejścia.", "401": "Nie można uwierzytelnić u dostawcy. Sprawdź konfigurację klucza API.", "402": "Wygląda na to, że wyczerpałeś środki/kredyty na swoim koncie. Przejdź do dostawcy i dodaj więcej, aby kontynuować.", "403": "Brak autoryzacji. Twój klucz API jest ważny, ale dostawca odmówił ukończenia tego żądania.", "429": "Zbyt wiele żądań. Dostawca ogranicza Ci szybkość żądań. Poczekaj chwilę przed następnym wywołaniem API.", "500": "Błąd serwera dostawcy. Po stronie dostawcy coś się nie powiodło, w Twoim żądaniu nie ma nic złego.", + "docs": "Dokumentacja", + "goToSettings": "Ustawienia", "unknown": "Nieznany błąd API. Skontaktuj się z pomocą techniczną Roo Code.", "connection": "Błąd połączenia. Upewnij się, że masz działające połączenie internetowe.", "claudeCodeNotAuthenticated": "Musisz się zalogować, aby korzystać z Claude Code. Przejdź do Ustawień i kliknij \"Zaloguj się do Claude Code\", aby się uwierzytelnić." @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo chce zobaczyć pliki najwyższego poziomu w tym katalogu (poza obszarem roboczym)", "didViewTopLevelOutsideWorkspace": "Roo zobaczył pliki najwyższego poziomu w tym katalogu (poza obszarem roboczym)", "wantsToViewRecursiveOutsideWorkspace": "Roo chce rekurencyjnie zobaczyć wszystkie pliki w tym katalogu (poza obszarem roboczym)", - "didViewRecursiveOutsideWorkspace": "Roo rekurencyjnie zobaczył wszystkie pliki w tym katalogu (poza obszarem roboczym)" + "didViewRecursiveOutsideWorkspace": "Roo rekurencyjnie zobaczył wszystkie pliki w tym katalogu (poza obszarem roboczym)", + "wantsToViewMultipleDirectories": "Roo chce wyświetlić wiele katalogów", + "didViewMultipleDirectories": "Roo wyświetlił wiele katalogów" }, "commandOutput": "Wyjście polecenia", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 2ca72ebd970..649d196c34e 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo quer visualizar os arquivos de nível superior neste diretório (fora do espaço de trabalho)", "didViewTopLevelOutsideWorkspace": "Roo visualizou os arquivos de nível superior neste diretório (fora do espaço de trabalho)", "wantsToViewRecursiveOutsideWorkspace": "Roo quer visualizar recursivamente todos os arquivos neste diretório (fora do espaço de trabalho)", - "didViewRecursiveOutsideWorkspace": "Roo visualizou recursivamente todos os arquivos neste diretório (fora do espaço de trabalho)" + "didViewRecursiveOutsideWorkspace": "Roo visualizou recursivamente todos os arquivos neste diretório (fora do espaço de trabalho)", + "wantsToViewMultipleDirectories": "Roo quer visualizar vários diretórios", + "didViewMultipleDirectories": "Roo visualizou vários diretórios" }, "commandOutput": "Saída do comando", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index 347bf1be81e..b1187f49f45 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -213,7 +213,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo хочет просмотреть файлы верхнего уровня в этой директории (вне рабочего пространства)", "didViewTopLevelOutsideWorkspace": "Roo просмотрел файлы верхнего уровня в этой директории (вне рабочего пространства)", "wantsToViewRecursiveOutsideWorkspace": "Roo хочет рекурсивно просмотреть все файлы в этой директории (вне рабочего пространства)", - "didViewRecursiveOutsideWorkspace": "Roo рекурсивно просмотрел все файлы в этой директории (вне рабочего пространства)" + "didViewRecursiveOutsideWorkspace": "Roo рекурсивно просмотрел все файлы в этой директории (вне рабочего пространства)", + "wantsToViewMultipleDirectories": "Roo хочет просмотреть несколько директорий", + "didViewMultipleDirectories": "Roo просмотрел несколько директорий" }, "commandOutput": "Вывод команды", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 2301541cdff..53540ae988b 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo bu dizindeki (çalışma alanı dışında) üst düzey dosyaları görüntülemek istiyor", "didViewTopLevelOutsideWorkspace": "Roo bu dizindeki (çalışma alanı dışında) üst düzey dosyaları görüntüledi", "wantsToViewRecursiveOutsideWorkspace": "Roo bu dizindeki (çalışma alanı dışında) tüm dosyaları özyinelemeli olarak görüntülemek istiyor", - "didViewRecursiveOutsideWorkspace": "Roo bu dizindeki (çalışma alanı dışında) tüm dosyaları özyinelemeli olarak görüntüledi" + "didViewRecursiveOutsideWorkspace": "Roo bu dizindeki (çalışma alanı dışında) tüm dosyaları özyinelemeli olarak görüntüledi", + "wantsToViewMultipleDirectories": "Roo birden fazla dizini görüntülemek istiyor", + "didViewMultipleDirectories": "Roo birden fazla dizini görüntüledi" }, "commandOutput": "Komut Çıktısı", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 33b13d9b269..5b8f514af07 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "Roo muốn xem các tệp cấp cao nhất trong thư mục này (ngoài không gian làm việc)", "didViewTopLevelOutsideWorkspace": "Roo đã xem các tệp cấp cao nhất trong thư mục này (ngoài không gian làm việc)", "wantsToViewRecursiveOutsideWorkspace": "Roo muốn xem đệ quy tất cả các tệp trong thư mục này (ngoài không gian làm việc)", - "didViewRecursiveOutsideWorkspace": "Roo đã xem đệ quy tất cả các tệp trong thư mục này (ngoài không gian làm việc)" + "didViewRecursiveOutsideWorkspace": "Roo đã xem đệ quy tất cả các tệp trong thư mục này (ngoài không gian làm việc)", + "wantsToViewMultipleDirectories": "Roo muốn xem nhiều thư mục", + "didViewMultipleDirectories": "Roo đã xem nhiều thư mục" }, "commandOutput": "Kết quả lệnh", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 7a94bfb48d7..83b3ec70fd3 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -218,7 +218,9 @@ "wantsToViewTopLevelOutsideWorkspace": "需要查看目录文件列表(工作区外)", "didViewTopLevelOutsideWorkspace": "已查看目录文件列表(工作区外)", "wantsToViewRecursiveOutsideWorkspace": "需要查看目录所有文件(工作区外)", - "didViewRecursiveOutsideWorkspace": "已查看目录所有文件(工作区外)" + "didViewRecursiveOutsideWorkspace": "已查看目录所有文件(工作区外)", + "wantsToViewMultipleDirectories": "Roo 想要查看多个目录", + "didViewMultipleDirectories": "Roo 查看了多个目录" }, "commandOutput": "命令输出", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 926cb105fb4..3510b433b29 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -156,14 +156,14 @@ "rateLimitWait": "速率限制", "errorTitle": "供應商錯誤 {{code}}", "errorMessage": { - "docs": "說明文件", - "goToSettings": "設定", "400": "供應商無法按照此方式處理請求。請停止工作並嘗試其他方法。", "401": "無法向供應商進行身份驗證。請檢查您的 API 金鑰設定。", "402": "您的帳戶資金/額度似乎已用盡。請前往供應商增加額度以繼續。", "403": "無權存取。您的 API 金鑰有效,但供應商拒絕完成此請求。", "429": "請求次數過多。供應商已對您的請求進行速率限制。請在下一次 API 呼叫前稍候。", "500": "供應商伺服器錯誤。伺服器端發生問題,您的請求沒有問題。", + "docs": "說明文件", + "goToSettings": "設定", "connection": "連線錯誤。請確保您有可用的網際網路連線。", "unknown": "未知 API 錯誤。請聯絡 Roo Code 技術支援。", "claudeCodeNotAuthenticated": "您需要登入才能使用 Claude Code。前往設定並點選「登入 Claude Code」以進行驗證。" @@ -241,7 +241,9 @@ "wantsToSearch": "Roo 想要在此目錄中搜尋 {{regex}}", "didSearch": "Roo 已在此目錄中搜尋 {{regex}}", "wantsToSearchOutsideWorkspace": "Roo 想要在此目錄(工作區外)中搜尋 {{regex}}", - "didSearchOutsideWorkspace": "Roo 已在此目錄(工作區外)中搜尋 {{regex}}" + "didSearchOutsideWorkspace": "Roo 已在此目錄(工作區外)中搜尋 {{regex}}", + "wantsToViewMultipleDirectories": "Roo 想要查看多個目錄", + "didViewMultipleDirectories": "Roo 查看了多個目錄" }, "codebaseSearch": { "wantsToSearch": "Roo 想要在程式碼庫中搜尋 {{query}}", From 51eb54d52c1846ab20b8511a2df58aa4c0590c6c Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Thu, 5 Feb 2026 22:25:00 -0700 Subject: [PATCH 3/8] refactor: consolidate duplicate listFiles batch-handling blocks in ChatRow Merge the separate listFilesTopLevel and listFilesRecursive case blocks into a single combined case with shared batch-detection logic, selecting the icon and translation key based on the tool type. This removes the duplicated isBatchDirRequest check and BatchListFilesPermission render. --- webview-ui/src/components/chat/ChatRow.tsx | 81 +++++++--------------- 1 file changed, 25 insertions(+), 56 deletions(-) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index b01b3a2775f..6a8a4d685c9 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -742,51 +742,12 @@ export const ChatRowContent = ({ ) } - case "listFilesTopLevel": { - // Check if this is a batch directory listing request - const isBatchDirRequest = message.type === "ask" && tool.batchDirs && Array.isArray(tool.batchDirs) - - if (isBatchDirRequest) { - return ( - <> -
- - - {t("chat:directoryOperations.wantsToViewMultipleDirectories")} - -
- - - ) - } - - return ( - <> -
- - - {message.type === "ask" - ? tool.isOutsideWorkspace - ? t("chat:directoryOperations.wantsToViewTopLevelOutsideWorkspace") - : t("chat:directoryOperations.wantsToViewTopLevel") - : tool.isOutsideWorkspace - ? t("chat:directoryOperations.didViewTopLevelOutsideWorkspace") - : t("chat:directoryOperations.didViewTopLevel")} - -
-
- -
- - ) - } + case "listFilesTopLevel": case "listFilesRecursive": { + const isRecursive = tool.tool === "listFilesRecursive" + const DirIcon = isRecursive ? FolderTree : ListTree + const dirIconLabel = isRecursive ? "Folder tree icon" : "List files icon" + // Check if this is a batch directory listing request const isBatchDirRequest = message.type === "ask" && tool.batchDirs && Array.isArray(tool.batchDirs) @@ -794,7 +755,7 @@ export const ChatRowContent = ({ return ( <>
- + {t("chat:directoryOperations.wantsToViewMultipleDirectories")} @@ -804,25 +765,33 @@ export const ChatRowContent = ({ ) } + const labelKey = isRecursive + ? message.type === "ask" + ? tool.isOutsideWorkspace + ? "chat:directoryOperations.wantsToViewRecursiveOutsideWorkspace" + : "chat:directoryOperations.wantsToViewRecursive" + : tool.isOutsideWorkspace + ? "chat:directoryOperations.didViewRecursiveOutsideWorkspace" + : "chat:directoryOperations.didViewRecursive" + : message.type === "ask" + ? tool.isOutsideWorkspace + ? "chat:directoryOperations.wantsToViewTopLevelOutsideWorkspace" + : "chat:directoryOperations.wantsToViewTopLevel" + : tool.isOutsideWorkspace + ? "chat:directoryOperations.didViewTopLevelOutsideWorkspace" + : "chat:directoryOperations.didViewTopLevel" + return ( <>
- - - {message.type === "ask" - ? tool.isOutsideWorkspace - ? t("chat:directoryOperations.wantsToViewRecursiveOutsideWorkspace") - : t("chat:directoryOperations.wantsToViewRecursive") - : tool.isOutsideWorkspace - ? t("chat:directoryOperations.didViewRecursiveOutsideWorkspace") - : t("chat:directoryOperations.didViewRecursive")} - + + {t(labelKey)}
From 2e02d04d7ddb5d381c494f327e3284ff02dcbb30 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Thu, 5 Feb 2026 22:38:34 -0700 Subject: [PATCH 4/8] feat: batch consecutive file-edit tool calls into single UI block Add edit-file batching in ChatView groupedMessages that consolidates consecutive editedExistingFile, appliedDiff, newFileCreated, insertContent, and searchAndReplace asks into a single BatchDiffApproval block. Move batchDiffs detection in ChatRow above the switch statement so it applies to any file-edit tool type. --- webview-ui/src/components/chat/ChatRow.tsx | 28 ++++--- webview-ui/src/components/chat/ChatView.tsx | 82 ++++++++++++++++++++- 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 6a8a4d685c9..a57328427f4 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -420,24 +420,22 @@ export const ChatRowContent = ({ style={{ color: "var(--vscode-foreground)", marginBottom: "-1.5px" }}> ) + // Handle batch diffs for any file-edit tool type + if (message.type === "ask" && tool.batchDiffs && Array.isArray(tool.batchDiffs)) { + return ( + <> +
+ + {t("chat:fileOperations.wantsToApplyBatchChanges")} +
+ + + ) + } + switch (tool.tool as string) { case "editedExistingFile": case "appliedDiff": - // Check if this is a batch diff request - if (message.type === "ask" && tool.batchDiffs && Array.isArray(tool.batchDiffs)) { - return ( - <> -
- - - {t("chat:fileOperations.wantsToApplyBatchChanges")} - -
- - - ) - } - // Regular single file diff return ( <> diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 9920ce15a98..8a011052736 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -1167,6 +1167,26 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + if (msg.type !== "ask" || msg.ask !== "tool") return false + try { + const tool = JSON.parse(msg.text || "{}") + return editFileTools.has(tool.tool) && !tool.batchDiffs // Don't re-batch already batched + } catch { + return false + } + } + // Consolidate consecutive read_file ask messages into batches const readFileBatched: ClineMessage[] = [] let i = 0 @@ -1226,7 +1246,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction 1) { + // Create a synthetic batch message with batchDiffs + const batchDiffs = batch.map((batchMsg) => { + try { + const tool = JSON.parse(batchMsg.text || "{}") + return { + path: tool.path || "", + changeCount: 1, + key: tool.path || "", + content: tool.content || tool.diff || "", + diffStats: tool.diffStats, + } + } catch { + return { path: "", changeCount: 0, key: "", content: "" } + } + }) + + // Use the first message as the base, but add batchDiffs + const firstTool = JSON.parse(msg.text || "{}") + const syntheticMessage: ClineMessage = { + ...msg, + text: JSON.stringify({ + ...firstTool, + batchDiffs, + }), + // Store original messages for response handling + _batchedMessages: batch, + } as ClineMessage & { _batchedMessages: ClineMessage[] } + + result.push(syntheticMessage) + i = j // Skip past all batched messages + } else { + // Single file-edit ask, keep as-is result.push(msg) i++ } From 3034b55f1dedbddf8c7bbba4b0053786346f7d3e Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Thu, 5 Feb 2026 23:33:33 -0700 Subject: [PATCH 5/8] refactor: extract batchConsecutive utility, fix batch UI issues - Extract generic batchConsecutive() utility from 3 identical while-loops - Fix React key collisions in BatchListFilesPermission, BatchFilePermission, BatchDiffApproval - Normalize language prop to "shellsession" (was "shell-session" for top-level) - Remove unused _batchedMessages property from synthetic messages - Remove dead didViewMultipleDirectories i18n key from all 18 locale files - Add batch button text for listFilesTopLevel/listFilesRecursive - Add batchConsecutive utility tests (6 cases) --- .../src/components/chat/BatchDiffApproval.tsx | 4 +- .../components/chat/BatchFilePermission.tsx | 4 +- .../chat/BatchListFilesPermission.tsx | 4 +- webview-ui/src/components/chat/ChatRow.tsx | 2 +- webview-ui/src/components/chat/ChatView.tsx | 230 ++++++------------ webview-ui/src/i18n/locales/ca/chat.json | 3 +- webview-ui/src/i18n/locales/de/chat.json | 3 +- webview-ui/src/i18n/locales/en/chat.json | 1 - webview-ui/src/i18n/locales/es/chat.json | 3 +- webview-ui/src/i18n/locales/fr/chat.json | 3 +- webview-ui/src/i18n/locales/hi/chat.json | 3 +- webview-ui/src/i18n/locales/id/chat.json | 3 +- webview-ui/src/i18n/locales/it/chat.json | 3 +- webview-ui/src/i18n/locales/ja/chat.json | 3 +- webview-ui/src/i18n/locales/ko/chat.json | 3 +- webview-ui/src/i18n/locales/nl/chat.json | 3 +- webview-ui/src/i18n/locales/pl/chat.json | 3 +- webview-ui/src/i18n/locales/pt-BR/chat.json | 3 +- webview-ui/src/i18n/locales/ru/chat.json | 3 +- webview-ui/src/i18n/locales/tr/chat.json | 3 +- webview-ui/src/i18n/locales/vi/chat.json | 3 +- webview-ui/src/i18n/locales/zh-CN/chat.json | 3 +- webview-ui/src/i18n/locales/zh-TW/chat.json | 3 +- .../utils/__tests__/batchConsecutive.spec.ts | 74 ++++++ webview-ui/src/utils/batchConsecutive.ts | 44 ++++ 25 files changed, 214 insertions(+), 200 deletions(-) create mode 100644 webview-ui/src/utils/__tests__/batchConsecutive.spec.ts create mode 100644 webview-ui/src/utils/batchConsecutive.ts diff --git a/webview-ui/src/components/chat/BatchDiffApproval.tsx b/webview-ui/src/components/chat/BatchDiffApproval.tsx index a88914cd88a..f128e4310d3 100644 --- a/webview-ui/src/components/chat/BatchDiffApproval.tsx +++ b/webview-ui/src/components/chat/BatchDiffApproval.tsx @@ -35,12 +35,12 @@ export const BatchDiffApproval = memo(({ files = [], ts }: BatchDiffApprovalProp return (
- {files.map((file) => { + {files.map((file, index) => { // Use backend-provided unified diff only. Stats also provided by backend. const unified = file.content || "" return ( -
+
{/* Individual files */}
- {files.map((file) => { + {files.map((file, index) => { return ( -
+
vscode.postMessage({ type: "openFile", text: file.content })}> diff --git a/webview-ui/src/components/chat/BatchListFilesPermission.tsx b/webview-ui/src/components/chat/BatchListFilesPermission.tsx index a1d909a5240..1645bc85d15 100644 --- a/webview-ui/src/components/chat/BatchListFilesPermission.tsx +++ b/webview-ui/src/components/chat/BatchListFilesPermission.tsx @@ -23,9 +23,9 @@ export const BatchListFilesPermission = memo(({ dirs = [], ts }: BatchListFilesP return (
- {dirs.map((dir) => { + {dirs.map((dir, index) => { return ( -
+
diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index a57328427f4..0772d52f15a 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -789,7 +789,7 @@ export const ChatRowContent = ({ diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index 8a011052736..cc9560250fd 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -11,6 +11,7 @@ import { Trans } from "react-i18next" import { useDebounceEffect } from "@src/utils/useDebounceEffect" import { appendImages } from "@src/utils/imageUtils" import { getCostBreakdownIfNeeded } from "@src/utils/costFormatting" +import { batchConsecutive } from "@src/utils/batchConsecutive" import type { ClineAsk, ClineSayTool, ClineMessage, ExtensionMessage, AudioType } from "@roo-code/types" @@ -328,6 +329,16 @@ const ChatViewComponent: React.ForwardRefRenderFunction { + const batchFiles = batch.map((batchMsg) => { + try { + const tool = JSON.parse(batchMsg.text || "{}") + return { + path: tool.path || "", + lineSnippet: tool.reason || "", + isOutsideWorkspace: tool.isOutsideWorkspace || false, + key: `${tool.path}${tool.reason ? ` (${tool.reason})` : ""}`, + content: tool.content || "", + } + } catch { + return { path: "", lineSnippet: "", key: "", content: "" } } + }) - if (batch.length > 1) { - // Create a synthetic batch message - const batchFiles = batch.map((batchMsg) => { - try { - const tool = JSON.parse(batchMsg.text || "{}") - return { - path: tool.path || "", - lineSnippet: tool.reason || "", - isOutsideWorkspace: tool.isOutsideWorkspace || false, - key: `${tool.path}${tool.reason ? ` (${tool.reason})` : ""}`, - content: tool.content || "", - } - } catch { - return { path: "", lineSnippet: "", key: "", content: "" } - } - }) - - // Use the first message as the base, but add batchFiles - const firstTool = JSON.parse(msg.text || "{}") - const syntheticMessage: ClineMessage = { - ...msg, - text: JSON.stringify({ - ...firstTool, - batchFiles, - }), - // Store original messages for response handling - _batchedMessages: batch, - } as ClineMessage & { _batchedMessages: ClineMessage[] } - - readFileBatched.push(syntheticMessage) - i = j // Skip past all batched messages - } else { - // Single read_file ask, keep as-is - readFileBatched.push(msg) - i++ - } - } else { - readFileBatched.push(msg) - i++ + const firstTool = JSON.parse(batch[0].text || "{}") + return { + ...batch[0], + text: JSON.stringify({ ...firstTool, batchFiles }), } } - // Consolidate consecutive list_files ask messages into batches - const listFilesBatched: ClineMessage[] = [] - i = 0 - while (i < readFileBatched.length) { - const msg = readFileBatched[i] - - // Check if this starts a sequence of list_files asks - if (isListFilesAsk(msg)) { - // Collect all consecutive list_files asks - const batch: ClineMessage[] = [msg] - let j = i + 1 - while (j < readFileBatched.length && isListFilesAsk(readFileBatched[j])) { - batch.push(readFileBatched[j]) - j++ + // Synthesize a batch of consecutive list_files asks into a single message + const synthesizeListFilesBatch = (batch: ClineMessage[]): ClineMessage => { + const batchDirs = batch.map((batchMsg) => { + try { + const tool = JSON.parse(batchMsg.text || "{}") + return { + path: tool.path || "", + recursive: tool.tool === "listFilesRecursive", + isOutsideWorkspace: tool.isOutsideWorkspace || false, + key: tool.path || "", + } + } catch { + return { path: "", recursive: false, key: "" } } + }) - if (batch.length > 1) { - // Create a synthetic batch message - const batchDirs = batch.map((batchMsg) => { - try { - const tool = JSON.parse(batchMsg.text || "{}") - return { - path: tool.path || "", - recursive: tool.tool === "listFilesRecursive", - isOutsideWorkspace: tool.isOutsideWorkspace || false, - key: tool.path || "", - } - } catch { - return { path: "", recursive: false, key: "" } - } - }) - - // Use the first message as the base, but add batchDirs - const firstTool = JSON.parse(msg.text || "{}") - const syntheticMessage: ClineMessage = { - ...msg, - text: JSON.stringify({ - ...firstTool, - batchDirs, - }), - // Store original messages for response handling - _batchedMessages: batch, - } as ClineMessage & { _batchedMessages: ClineMessage[] } - - listFilesBatched.push(syntheticMessage) - i = j // Skip past all batched messages - } else { - // Single list_files ask, keep as-is - listFilesBatched.push(msg) - i++ - } - } else { - listFilesBatched.push(msg) - i++ + const firstTool = JSON.parse(batch[0].text || "{}") + return { + ...batch[0], + text: JSON.stringify({ ...firstTool, batchDirs }), } } - // Consolidate consecutive file-edit ask messages into batches - const result: ClineMessage[] = [] - i = 0 - while (i < listFilesBatched.length) { - const msg = listFilesBatched[i] - - // Check if this starts a sequence of file-edit asks - if (isEditFileAsk(msg)) { - // Collect all consecutive file-edit asks - const batch: ClineMessage[] = [msg] - let j = i + 1 - while (j < listFilesBatched.length && isEditFileAsk(listFilesBatched[j])) { - batch.push(listFilesBatched[j]) - j++ + // Synthesize a batch of consecutive file-edit asks into a single message + const synthesizeEditFileBatch = (batch: ClineMessage[]): ClineMessage => { + const batchDiffs = batch.map((batchMsg) => { + try { + const tool = JSON.parse(batchMsg.text || "{}") + return { + path: tool.path || "", + changeCount: 1, + key: tool.path || "", + content: tool.content || tool.diff || "", + diffStats: tool.diffStats, + } + } catch { + return { path: "", changeCount: 0, key: "", content: "" } } + }) - if (batch.length > 1) { - // Create a synthetic batch message with batchDiffs - const batchDiffs = batch.map((batchMsg) => { - try { - const tool = JSON.parse(batchMsg.text || "{}") - return { - path: tool.path || "", - changeCount: 1, - key: tool.path || "", - content: tool.content || tool.diff || "", - diffStats: tool.diffStats, - } - } catch { - return { path: "", changeCount: 0, key: "", content: "" } - } - }) - - // Use the first message as the base, but add batchDiffs - const firstTool = JSON.parse(msg.text || "{}") - const syntheticMessage: ClineMessage = { - ...msg, - text: JSON.stringify({ - ...firstTool, - batchDiffs, - }), - // Store original messages for response handling - _batchedMessages: batch, - } as ClineMessage & { _batchedMessages: ClineMessage[] } - - result.push(syntheticMessage) - i = j // Skip past all batched messages - } else { - // Single file-edit ask, keep as-is - result.push(msg) - i++ - } - } else { - result.push(msg) - i++ + const firstTool = JSON.parse(batch[0].text || "{}") + return { + ...batch[0], + text: JSON.stringify({ ...firstTool, batchDiffs }), } } + // Consolidate consecutive ask messages into batches + const readFileBatched = batchConsecutive(filtered, isReadFileAsk, synthesizeReadFileBatch) + const listFilesBatched = batchConsecutive(readFileBatched, isListFilesAsk, synthesizeListFilesBatch) + const result = batchConsecutive(listFilesBatched, isEditFileAsk, synthesizeEditFileBatch) + if (isCondensing) { result.push({ type: "say", diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index bb9eb344787..5adf053b306 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "Roo ha vist els fitxers de nivell superior en aquest directori (fora de l'espai de treball)", "wantsToViewRecursiveOutsideWorkspace": "Roo vol veure recursivament tots els fitxers en aquest directori (fora de l'espai de treball)", "didViewRecursiveOutsideWorkspace": "Roo ha vist recursivament tots els fitxers en aquest directori (fora de l'espai de treball)", - "wantsToViewMultipleDirectories": "Roo vol veure diversos directoris", - "didViewMultipleDirectories": "Roo ha vist diversos directoris" + "wantsToViewMultipleDirectories": "Roo vol veure diversos directoris" }, "commandOutput": "Sortida de la comanda", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index 054c8b5e038..e0f93dfe327 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "Roo hat die Dateien auf oberster Ebene in diesem Verzeichnis (außerhalb des Arbeitsbereichs) angezeigt", "wantsToViewRecursiveOutsideWorkspace": "Roo möchte rekursiv alle Dateien in diesem Verzeichnis (außerhalb des Arbeitsbereichs) anzeigen", "didViewRecursiveOutsideWorkspace": "Roo hat rekursiv alle Dateien in diesem Verzeichnis (außerhalb des Arbeitsbereichs) angezeigt", - "wantsToViewMultipleDirectories": "Roo möchte mehrere Verzeichnisse anzeigen", - "didViewMultipleDirectories": "Roo hat mehrere Verzeichnisse angezeigt" + "wantsToViewMultipleDirectories": "Roo möchte mehrere Verzeichnisse anzeigen" }, "commandOutput": "Befehlsausgabe", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index b60ad596208..04d8769c0bc 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -236,7 +236,6 @@ "wantsToViewRecursiveOutsideWorkspace": "Roo wants to recursively view all files in this directory (outside workspace)", "didViewRecursiveOutsideWorkspace": "Roo recursively viewed all files in this directory (outside workspace)", "wantsToViewMultipleDirectories": "Roo wants to view multiple directories", - "didViewMultipleDirectories": "Roo viewed multiple directories", "wantsToSearch": "Roo wants to search this directory for {{regex}}", "didSearch": "Roo searched this directory for {{regex}}", "wantsToSearchOutsideWorkspace": "Roo wants to search this directory (outside workspace) for {{regex}}", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index d4e42724550..b7329cdab1b 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "Roo vio los archivos de nivel superior en este directorio (fuera del espacio de trabajo)", "wantsToViewRecursiveOutsideWorkspace": "Roo quiere ver recursivamente todos los archivos en este directorio (fuera del espacio de trabajo)", "didViewRecursiveOutsideWorkspace": "Roo vio recursivamente todos los archivos en este directorio (fuera del espacio de trabajo)", - "wantsToViewMultipleDirectories": "Roo quiere ver varios directorios", - "didViewMultipleDirectories": "Roo vio varios directorios" + "wantsToViewMultipleDirectories": "Roo quiere ver varios directorios" }, "commandOutput": "Salida del comando", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 95dc15a67b0..74283e7d4a1 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "Roo a vu les fichiers de premier niveau dans ce répertoire (hors espace de travail)", "wantsToViewRecursiveOutsideWorkspace": "Roo veut voir récursivement tous les fichiers dans ce répertoire (hors espace de travail)", "didViewRecursiveOutsideWorkspace": "Roo a vu récursivement tous les fichiers dans ce répertoire (hors espace de travail)", - "wantsToViewMultipleDirectories": "Roo veut voir plusieurs répertoires", - "didViewMultipleDirectories": "Roo a vu plusieurs répertoires" + "wantsToViewMultipleDirectories": "Roo veut voir plusieurs répertoires" }, "commandOutput": "Sortie de la Commande", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index cf385e7dafa..a6c7683c849 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "Roo ने इस निर्देशिका (कार्यक्षेत्र के बाहर) में शीर्ष स्तर की फ़ाइलें देखीं", "wantsToViewRecursiveOutsideWorkspace": "Roo इस निर्देशिका (कार्यक्षेत्र के बाहर) में सभी फ़ाइलों को पुनरावर्ती रूप से देखना चाहता है", "didViewRecursiveOutsideWorkspace": "Roo ने इस निर्देशिका (कार्यक्षेत्र के बाहर) में सभी फ़ाइलों को पुनरावर्ती रूप से देखा", - "wantsToViewMultipleDirectories": "Roo कई डायरेक्ट्रीज़ देखना चाहता है", - "didViewMultipleDirectories": "Roo ने कई डायरेक्ट्रीज़ देखीं" + "wantsToViewMultipleDirectories": "Roo कई डायरेक्ट्रीज़ देखना चाहता है" }, "commandOutput": "कमांड आउटपुट", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 66ef4b30fec..4dfc357bf24 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -247,8 +247,7 @@ "didViewTopLevelOutsideWorkspace": "Roo melihat file tingkat atas di direktori ini (di luar workspace)", "wantsToViewRecursiveOutsideWorkspace": "Roo ingin melihat semua file secara rekursif di direktori ini (di luar workspace)", "didViewRecursiveOutsideWorkspace": "Roo melihat semua file secara rekursif di direktori ini (di luar workspace)", - "wantsToViewMultipleDirectories": "Roo ingin melihat beberapa direktori", - "didViewMultipleDirectories": "Roo melihat beberapa direktori" + "wantsToViewMultipleDirectories": "Roo ingin melihat beberapa direktori" }, "codebaseSearch": { "wantsToSearch": "Roo ingin mencari codebase untuk {{query}}", diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index b2078f37e9e..8aaa2ebac3a 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "Roo ha visualizzato i file di primo livello in questa directory (fuori dall'area di lavoro)", "wantsToViewRecursiveOutsideWorkspace": "Roo vuole visualizzare ricorsivamente tutti i file in questa directory (fuori dall'area di lavoro)", "didViewRecursiveOutsideWorkspace": "Roo ha visualizzato ricorsivamente tutti i file in questa directory (fuori dall'area di lavoro)", - "wantsToViewMultipleDirectories": "Roo vuole visualizzare più directory", - "didViewMultipleDirectories": "Roo ha visualizzato più directory" + "wantsToViewMultipleDirectories": "Roo vuole visualizzare più directory" }, "commandOutput": "Output del Comando", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index eee2b888fc3..48b13cfd66d 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "Rooはこのディレクトリ(ワークスペース外)のトップレベルファイルを表示しました", "wantsToViewRecursiveOutsideWorkspace": "Rooはこのディレクトリ(ワークスペース外)のすべてのファイルを再帰的に表示したい", "didViewRecursiveOutsideWorkspace": "Rooはこのディレクトリ(ワークスペース外)のすべてのファイルを再帰的に表示しました", - "wantsToViewMultipleDirectories": "Roo は複数のディレクトリを表示したい", - "didViewMultipleDirectories": "Roo は複数のディレクトリを表示しました" + "wantsToViewMultipleDirectories": "Roo は複数のディレクトリを表示したい" }, "commandOutput": "コマンド出力", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index 1227dd6c15e..eb037eefe16 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "Roo가 이 디렉토리(워크스페이스 외부)의 최상위 파일을 보았습니다", "wantsToViewRecursiveOutsideWorkspace": "Roo가 이 디렉토리(워크스페이스 외부)의 모든 파일을 재귀적으로 보고 싶어합니다", "didViewRecursiveOutsideWorkspace": "Roo가 이 디렉토리(워크스페이스 외부)의 모든 파일을 재귀적으로 보았습니다", - "wantsToViewMultipleDirectories": "Roo가 여러 디렉토리를 보려고 합니다", - "didViewMultipleDirectories": "Roo가 여러 디렉토리를 확인했습니다" + "wantsToViewMultipleDirectories": "Roo가 여러 디렉토리를 보려고 합니다" }, "commandOutput": "명령 출력", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index c6391805253..77d25310008 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -214,8 +214,7 @@ "didViewTopLevelOutsideWorkspace": "Roo heeft de bovenliggende bestanden in deze map (buiten werkruimte) bekeken", "wantsToViewRecursiveOutsideWorkspace": "Roo wil alle bestanden in deze map (buiten werkruimte) recursief bekijken", "didViewRecursiveOutsideWorkspace": "Roo heeft alle bestanden in deze map (buiten werkruimte) recursief bekeken", - "wantsToViewMultipleDirectories": "Roo wil meerdere mappen bekijken", - "didViewMultipleDirectories": "Roo heeft meerdere mappen bekeken" + "wantsToViewMultipleDirectories": "Roo wil meerdere mappen bekijken" }, "commandOutput": "Commando-uitvoer", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 74711695532..66801899e9c 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "Roo zobaczył pliki najwyższego poziomu w tym katalogu (poza obszarem roboczym)", "wantsToViewRecursiveOutsideWorkspace": "Roo chce rekurencyjnie zobaczyć wszystkie pliki w tym katalogu (poza obszarem roboczym)", "didViewRecursiveOutsideWorkspace": "Roo rekurencyjnie zobaczył wszystkie pliki w tym katalogu (poza obszarem roboczym)", - "wantsToViewMultipleDirectories": "Roo chce wyświetlić wiele katalogów", - "didViewMultipleDirectories": "Roo wyświetlił wiele katalogów" + "wantsToViewMultipleDirectories": "Roo chce wyświetlić wiele katalogów" }, "commandOutput": "Wyjście polecenia", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index 649d196c34e..d563aa6b367 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "Roo visualizou os arquivos de nível superior neste diretório (fora do espaço de trabalho)", "wantsToViewRecursiveOutsideWorkspace": "Roo quer visualizar recursivamente todos os arquivos neste diretório (fora do espaço de trabalho)", "didViewRecursiveOutsideWorkspace": "Roo visualizou recursivamente todos os arquivos neste diretório (fora do espaço de trabalho)", - "wantsToViewMultipleDirectories": "Roo quer visualizar vários diretórios", - "didViewMultipleDirectories": "Roo visualizou vários diretórios" + "wantsToViewMultipleDirectories": "Roo quer visualizar vários diretórios" }, "commandOutput": "Saída do comando", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index b1187f49f45..f599bacaa16 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -214,8 +214,7 @@ "didViewTopLevelOutsideWorkspace": "Roo просмотрел файлы верхнего уровня в этой директории (вне рабочего пространства)", "wantsToViewRecursiveOutsideWorkspace": "Roo хочет рекурсивно просмотреть все файлы в этой директории (вне рабочего пространства)", "didViewRecursiveOutsideWorkspace": "Roo рекурсивно просмотрел все файлы в этой директории (вне рабочего пространства)", - "wantsToViewMultipleDirectories": "Roo хочет просмотреть несколько директорий", - "didViewMultipleDirectories": "Roo просмотрел несколько директорий" + "wantsToViewMultipleDirectories": "Roo хочет просмотреть несколько директорий" }, "commandOutput": "Вывод команды", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index 53540ae988b..e4a9bf71f00 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "Roo bu dizindeki (çalışma alanı dışında) üst düzey dosyaları görüntüledi", "wantsToViewRecursiveOutsideWorkspace": "Roo bu dizindeki (çalışma alanı dışında) tüm dosyaları özyinelemeli olarak görüntülemek istiyor", "didViewRecursiveOutsideWorkspace": "Roo bu dizindeki (çalışma alanı dışında) tüm dosyaları özyinelemeli olarak görüntüledi", - "wantsToViewMultipleDirectories": "Roo birden fazla dizini görüntülemek istiyor", - "didViewMultipleDirectories": "Roo birden fazla dizini görüntüledi" + "wantsToViewMultipleDirectories": "Roo birden fazla dizini görüntülemek istiyor" }, "commandOutput": "Komut Çıktısı", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 5b8f514af07..7823801766c 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "Roo đã xem các tệp cấp cao nhất trong thư mục này (ngoài không gian làm việc)", "wantsToViewRecursiveOutsideWorkspace": "Roo muốn xem đệ quy tất cả các tệp trong thư mục này (ngoài không gian làm việc)", "didViewRecursiveOutsideWorkspace": "Roo đã xem đệ quy tất cả các tệp trong thư mục này (ngoài không gian làm việc)", - "wantsToViewMultipleDirectories": "Roo muốn xem nhiều thư mục", - "didViewMultipleDirectories": "Roo đã xem nhiều thư mục" + "wantsToViewMultipleDirectories": "Roo muốn xem nhiều thư mục" }, "commandOutput": "Kết quả lệnh", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 83b3ec70fd3..4e5002b33d5 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -219,8 +219,7 @@ "didViewTopLevelOutsideWorkspace": "已查看目录文件列表(工作区外)", "wantsToViewRecursiveOutsideWorkspace": "需要查看目录所有文件(工作区外)", "didViewRecursiveOutsideWorkspace": "已查看目录所有文件(工作区外)", - "wantsToViewMultipleDirectories": "Roo 想要查看多个目录", - "didViewMultipleDirectories": "Roo 查看了多个目录" + "wantsToViewMultipleDirectories": "Roo 想要查看多个目录" }, "commandOutput": "命令输出", "commandExecution": { diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 3510b433b29..49b459d54b7 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -242,8 +242,7 @@ "didSearch": "Roo 已在此目錄中搜尋 {{regex}}", "wantsToSearchOutsideWorkspace": "Roo 想要在此目錄(工作區外)中搜尋 {{regex}}", "didSearchOutsideWorkspace": "Roo 已在此目錄(工作區外)中搜尋 {{regex}}", - "wantsToViewMultipleDirectories": "Roo 想要查看多個目錄", - "didViewMultipleDirectories": "Roo 查看了多個目錄" + "wantsToViewMultipleDirectories": "Roo 想要查看多個目錄" }, "codebaseSearch": { "wantsToSearch": "Roo 想要在程式碼庫中搜尋 {{query}}", diff --git a/webview-ui/src/utils/__tests__/batchConsecutive.spec.ts b/webview-ui/src/utils/__tests__/batchConsecutive.spec.ts new file mode 100644 index 00000000000..3bb5a69d2f3 --- /dev/null +++ b/webview-ui/src/utils/__tests__/batchConsecutive.spec.ts @@ -0,0 +1,74 @@ +import type { ClineMessage } from "@roo-code/types" + +import { batchConsecutive } from "../batchConsecutive" + +/** Helper: create a minimal ClineMessage with an identifiable text field. */ +function msg(text: string, type: ClineMessage["type"] = "say"): ClineMessage { + return { ts: Date.now(), type, text } +} + +/** Predicate: matches messages whose text starts with "match". */ +const isMatch = (m: ClineMessage) => !!m.text?.startsWith("match") + +/** Synthesize: merges a batch into a single message with a "BATCH:" marker. */ +const synthesizeBatch = (batch: ClineMessage[]): ClineMessage => ({ + ...batch[0], + text: `BATCH:${batch.map((m) => m.text).join(",")}`, +}) + +describe("batchConsecutive", () => { + test("empty input returns empty output", () => { + expect(batchConsecutive([], isMatch, synthesizeBatch)).toEqual([]) + }) + + test("no matches returns passthrough", () => { + const messages = [msg("a"), msg("b"), msg("c")] + const result = batchConsecutive(messages, isMatch, synthesizeBatch) + expect(result).toEqual(messages) + }) + + test("single match is passed through without batching", () => { + const messages = [msg("a"), msg("match-1"), msg("b")] + const result = batchConsecutive(messages, isMatch, synthesizeBatch) + expect(result).toHaveLength(3) + expect(result[1].text).toBe("match-1") + }) + + test("two consecutive matches produce one synthetic message", () => { + const messages = [msg("a"), msg("match-1"), msg("match-2"), msg("b")] + const result = batchConsecutive(messages, isMatch, synthesizeBatch) + expect(result).toHaveLength(3) + expect(result[0].text).toBe("a") + expect(result[1].text).toBe("BATCH:match-1,match-2") + expect(result[2].text).toBe("b") + }) + + test("non-consecutive matches are not batched", () => { + const messages = [msg("match-1"), msg("other"), msg("match-2")] + const result = batchConsecutive(messages, isMatch, synthesizeBatch) + expect(result).toHaveLength(3) + expect(result[0].text).toBe("match-1") + expect(result[1].text).toBe("other") + expect(result[2].text).toBe("match-2") + }) + + test("mixed sequences are correctly interleaved", () => { + const messages = [ + msg("match-1"), + msg("match-2"), + msg("match-3"), + msg("other-1"), + msg("match-4"), + msg("other-2"), + msg("match-5"), + msg("match-6"), + ] + const result = batchConsecutive(messages, isMatch, synthesizeBatch) + expect(result).toHaveLength(5) + expect(result[0].text).toBe("BATCH:match-1,match-2,match-3") + expect(result[1].text).toBe("other-1") + expect(result[2].text).toBe("match-4") // single — not batched + expect(result[3].text).toBe("other-2") + expect(result[4].text).toBe("BATCH:match-5,match-6") + }) +}) diff --git a/webview-ui/src/utils/batchConsecutive.ts b/webview-ui/src/utils/batchConsecutive.ts new file mode 100644 index 00000000000..e030ad0c707 --- /dev/null +++ b/webview-ui/src/utils/batchConsecutive.ts @@ -0,0 +1,44 @@ +import type { ClineMessage } from "@roo-code/types" + +/** + * Walk a message array and batch runs of consecutive messages that match + * `predicate` into synthetic messages produced by `synthesize`. + * + * - Runs of length 1 are passed through unchanged. + * - Runs of length >= 2 are replaced by a single synthetic message. + * - Non-matching messages are preserved in-order. + */ +export function batchConsecutive( + messages: ClineMessage[], + predicate: (msg: ClineMessage) => boolean, + synthesize: (batch: ClineMessage[]) => ClineMessage, +): ClineMessage[] { + const result: ClineMessage[] = [] + let i = 0 + + while (i < messages.length) { + if (predicate(messages[i])) { + // Collect consecutive matches into a batch + const batch: ClineMessage[] = [messages[i]] + let j = i + 1 + + while (j < messages.length && predicate(messages[j])) { + batch.push(messages[j]) + j++ + } + + if (batch.length > 1) { + result.push(synthesize(batch)) + } else { + result.push(batch[0]) + } + + i = j + } else { + result.push(messages[i]) + i++ + } + } + + return result +} From 9ffc778491845f0ff2304a2118246a9d5d7848dd Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Fri, 6 Feb 2026 17:12:44 -0700 Subject: [PATCH 6/8] fix: audit improvements for batch tool-call UI - Make batchConsecutive() generic instead of ClineMessage-specific - Add batch-aware button text for edit-file batches ("Save All"/"Deny All") - Add dedicated list-batch/edit-batch i18n keys (stop reusing read-batch) - Add JSON.parse defense-in-depth in all three synthesizers - Fix mixed list_files batch icon to default to FolderTree - Add 6 missing test cases (all-match, immutability, spy, single-dir) --- webview-ui/src/components/chat/ChatRow.tsx | 12 +++- webview-ui/src/components/chat/ChatView.tsx | 33 +++++++++-- .../BatchListFilesPermission.spec.tsx | 24 ++++++++ webview-ui/src/i18n/locales/en/chat.json | 16 +++++ .../utils/__tests__/batchConsecutive.spec.ts | 58 ++++++++++++++++--- webview-ui/src/utils/batchConsecutive.ts | 30 ++++------ 6 files changed, 140 insertions(+), 33 deletions(-) diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index 0772d52f15a..b4342f2edfc 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -743,12 +743,20 @@ export const ChatRowContent = ({ case "listFilesTopLevel": case "listFilesRecursive": { const isRecursive = tool.tool === "listFilesRecursive" - const DirIcon = isRecursive ? FolderTree : ListTree - const dirIconLabel = isRecursive ? "Folder tree icon" : "List files icon" // Check if this is a batch directory listing request const isBatchDirRequest = message.type === "ask" && tool.batchDirs && Array.isArray(tool.batchDirs) + // When batching, check if all dirs share the same recursive value + const allTopLevel = tool.batchDirs?.every((d: { recursive: boolean }) => !d.recursive) + const DirIcon = isBatchDirRequest && !allTopLevel ? FolderTree : isRecursive ? FolderTree : ListTree + const dirIconLabel = + isBatchDirRequest && !allTopLevel + ? "Folder tree icon" + : isRecursive + ? "Folder tree icon" + : "List files icon" + if (isBatchDirRequest) { return ( <> diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index cc9560250fd..ff64e060686 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -312,6 +312,14 @@ const ChatViewComponent: React.ForwardRefRenderFunction { // All 3 dirs should be inside this container expect(container?.querySelectorAll(".flex.items-center.gap-2")).toHaveLength(mockDirs.length) }) + + it("renders a single directory", () => { + const singleDir = [ + { + key: "apps/cli", + path: "apps/cli", + recursive: false, + isOutsideWorkspace: false, + }, + ] + + render( + + + , + ) + + expect(screen.getByText("apps/cli")).toBeInTheDocument() + + // Single directory should still be rendered inside the container + const bordered = screen.getByText("apps/cli").closest(".border.border-border.rounded-md") + expect(bordered).toBeInTheDocument() + expect(bordered?.querySelectorAll(".flex.items-center.gap-2")).toHaveLength(1) + }) }) diff --git a/webview-ui/src/i18n/locales/en/chat.json b/webview-ui/src/i18n/locales/en/chat.json index 04d8769c0bc..4313a04db5b 100644 --- a/webview-ui/src/i18n/locales/en/chat.json +++ b/webview-ui/src/i18n/locales/en/chat.json @@ -74,6 +74,22 @@ "title": "Deny All" } }, + "list-batch": { + "approve": { + "title": "Approve All" + }, + "deny": { + "title": "Deny All" + } + }, + "edit-batch": { + "approve": { + "title": "Save All" + }, + "deny": { + "title": "Deny All" + } + }, "runCommand": { "title": "Run", "tooltip": "Execute this command" diff --git a/webview-ui/src/utils/__tests__/batchConsecutive.spec.ts b/webview-ui/src/utils/__tests__/batchConsecutive.spec.ts index 3bb5a69d2f3..b3919fdbd67 100644 --- a/webview-ui/src/utils/__tests__/batchConsecutive.spec.ts +++ b/webview-ui/src/utils/__tests__/batchConsecutive.spec.ts @@ -1,17 +1,21 @@ -import type { ClineMessage } from "@roo-code/types" - import { batchConsecutive } from "../batchConsecutive" -/** Helper: create a minimal ClineMessage with an identifiable text field. */ -function msg(text: string, type: ClineMessage["type"] = "say"): ClineMessage { +interface TestItem { + ts: number + type: string + text: string +} + +/** Helper: create a minimal test item with an identifiable text field. */ +function msg(text: string, type = "say"): TestItem { return { ts: Date.now(), type, text } } -/** Predicate: matches messages whose text starts with "match". */ -const isMatch = (m: ClineMessage) => !!m.text?.startsWith("match") +/** Predicate: matches items whose text starts with "match". */ +const isMatch = (m: TestItem) => !!m.text?.startsWith("match") -/** Synthesize: merges a batch into a single message with a "BATCH:" marker. */ -const synthesizeBatch = (batch: ClineMessage[]): ClineMessage => ({ +/** Synthesize: merges a batch into a single item with a "BATCH:" marker. */ +const synthesizeBatch = (batch: TestItem[]): TestItem => ({ ...batch[0], text: `BATCH:${batch.map((m) => m.text).join(",")}`, }) @@ -71,4 +75,42 @@ describe("batchConsecutive", () => { expect(result[3].text).toBe("other-2") expect(result[4].text).toBe("BATCH:match-5,match-6") }) + + test("all items match → single synthetic message", () => { + const items = [msg("match-1"), msg("match-2"), msg("match-3")] + const result = batchConsecutive(items, isMatch, synthesizeBatch) + expect(result).toHaveLength(1) + expect(result[0].text).toBe("BATCH:match-1,match-2,match-3") + }) + + test("does not mutate the input array", () => { + const items = [msg("match-1"), msg("match-2")] + const original = [...items] + batchConsecutive(items, isMatch, synthesizeBatch) + expect(items).toHaveLength(2) + expect(items).toEqual(original) + }) + + test("returns a new array, not the same reference", () => { + const items = [msg("a"), msg("b")] + const result = batchConsecutive(items, isMatch, synthesizeBatch) + expect(result).not.toBe(items) + }) + + test("synthesize callback receives the correct batches", () => { + const spy = vi.fn(synthesizeBatch) + const items = [msg("match-1"), msg("match-2"), msg("other"), msg("match-3"), msg("match-4")] + batchConsecutive(items, isMatch, spy) + expect(spy).toHaveBeenCalledTimes(2) + expect(spy.mock.calls[0][0]).toHaveLength(2) + expect(spy.mock.calls[1][0]).toHaveLength(2) + }) + + test("batch at the end of the array", () => { + const items = [msg("other"), msg("match-1"), msg("match-2")] + const result = batchConsecutive(items, isMatch, synthesizeBatch) + expect(result).toHaveLength(2) + expect(result[0].text).toBe("other") + expect(result[1].text).toBe("BATCH:match-1,match-2") + }) }) diff --git a/webview-ui/src/utils/batchConsecutive.ts b/webview-ui/src/utils/batchConsecutive.ts index e030ad0c707..336d8a74a6e 100644 --- a/webview-ui/src/utils/batchConsecutive.ts +++ b/webview-ui/src/utils/batchConsecutive.ts @@ -1,29 +1,23 @@ -import type { ClineMessage } from "@roo-code/types" - /** - * Walk a message array and batch runs of consecutive messages that match - * `predicate` into synthetic messages produced by `synthesize`. + * Walk an item array and batch runs of consecutive items that match + * `predicate` into synthetic items produced by `synthesize`. * * - Runs of length 1 are passed through unchanged. - * - Runs of length >= 2 are replaced by a single synthetic message. - * - Non-matching messages are preserved in-order. + * - Runs of length >= 2 are replaced by a single synthetic item. + * - Non-matching items are preserved in-order. */ -export function batchConsecutive( - messages: ClineMessage[], - predicate: (msg: ClineMessage) => boolean, - synthesize: (batch: ClineMessage[]) => ClineMessage, -): ClineMessage[] { - const result: ClineMessage[] = [] +export function batchConsecutive(items: T[], predicate: (item: T) => boolean, synthesize: (batch: T[]) => T): T[] { + const result: T[] = [] let i = 0 - while (i < messages.length) { - if (predicate(messages[i])) { + while (i < items.length) { + if (predicate(items[i])) { // Collect consecutive matches into a batch - const batch: ClineMessage[] = [messages[i]] + const batch: T[] = [items[i]] let j = i + 1 - while (j < messages.length && predicate(messages[j])) { - batch.push(messages[j]) + while (j < items.length && predicate(items[j])) { + batch.push(items[j]) j++ } @@ -35,7 +29,7 @@ export function batchConsecutive( i = j } else { - result.push(messages[i]) + result.push(items[i]) i++ } } From 946ab9fabedd390ed65650b880a45e9505171313 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Fri, 6 Feb 2026 17:50:41 -0700 Subject: [PATCH 7/8] chore: minor type cleanup (out-of-scope housekeeping) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Trim unused recursive/isOutsideWorkspace from DirPermissionItem interface - Remove 4 pre-existing `as any` casts in ChatView.tsx: - window cast → precise inline type - checkpoint bracket access → removed unnecessary casts - condensing message → `as ClineMessage` - debounce cancel → `.clear()` (correct API) - Update BatchListFilesPermission test data to match trimmed interface --- .../components/chat/BatchListFilesPermission.tsx | 2 -- webview-ui/src/components/chat/ChatView.tsx | 15 ++++++--------- .../__tests__/BatchListFilesPermission.spec.tsx | 8 -------- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/webview-ui/src/components/chat/BatchListFilesPermission.tsx b/webview-ui/src/components/chat/BatchListFilesPermission.tsx index 1645bc85d15..a5d08c244bb 100644 --- a/webview-ui/src/components/chat/BatchListFilesPermission.tsx +++ b/webview-ui/src/components/chat/BatchListFilesPermission.tsx @@ -5,8 +5,6 @@ import { PathTooltip } from "../ui/PathTooltip" interface DirPermissionItem { path: string - recursive: boolean - isOutsideWorkspace?: boolean key: string } diff --git a/webview-ui/src/components/chat/ChatView.tsx b/webview-ui/src/components/chat/ChatView.tsx index ff64e060686..05d0c4fe4a3 100644 --- a/webview-ui/src/components/chat/ChatView.tsx +++ b/webview-ui/src/components/chat/ChatView.tsx @@ -72,8 +72,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction { - const w = window as any - return w.AUDIO_BASE_URI || "" + return (window as unknown as { AUDIO_BASE_URI?: string }).AUDIO_BASE_URI || "" }) const { t } = useAppTranslation() @@ -976,10 +975,10 @@ const ChatViewComponent: React.ForwardRefRenderFunction { return () => { - if (scrollToBottomSmooth && typeof (scrollToBottomSmooth as any).cancel === "function") { - ;(scrollToBottomSmooth as any).cancel() - } + scrollToBottomSmooth.clear() } }, [scrollToBottomSmooth]) diff --git a/webview-ui/src/components/chat/__tests__/BatchListFilesPermission.spec.tsx b/webview-ui/src/components/chat/__tests__/BatchListFilesPermission.spec.tsx index dbfdd297b17..21ea05192f8 100644 --- a/webview-ui/src/components/chat/__tests__/BatchListFilesPermission.spec.tsx +++ b/webview-ui/src/components/chat/__tests__/BatchListFilesPermission.spec.tsx @@ -9,20 +9,14 @@ describe("BatchListFilesPermission", () => { { key: "apps/cli", path: "apps/cli", - recursive: false, - isOutsideWorkspace: false, }, { key: "apps/web-roo-code", path: "apps/web-roo-code", - recursive: false, - isOutsideWorkspace: false, }, { key: "packages/core", path: "packages/core", - recursive: true, - isOutsideWorkspace: false, }, ] @@ -90,8 +84,6 @@ describe("BatchListFilesPermission", () => { { key: "apps/cli", path: "apps/cli", - recursive: false, - isOutsideWorkspace: false, }, ] From c15377ec353aeb24ae99a1f979b595e54621522d Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Fri, 6 Feb 2026 18:41:17 -0700 Subject: [PATCH 8/8] i18n: add list-batch and edit-batch translations for all locales --- webview-ui/src/i18n/locales/ca/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/de/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/es/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/fr/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/hi/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/id/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/it/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/ja/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/ko/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/nl/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/pl/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/pt-BR/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/ru/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/tr/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/vi/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/zh-CN/chat.json | 16 ++++++++++++++++ webview-ui/src/i18n/locales/zh-TW/chat.json | 16 ++++++++++++++++ 17 files changed, 272 insertions(+) diff --git a/webview-ui/src/i18n/locales/ca/chat.json b/webview-ui/src/i18n/locales/ca/chat.json index 5adf053b306..00c9208d93f 100644 --- a/webview-ui/src/i18n/locales/ca/chat.json +++ b/webview-ui/src/i18n/locales/ca/chat.json @@ -440,6 +440,22 @@ "title": "Denegar tot" } }, + "list-batch": { + "approve": { + "title": "Aprovar tot" + }, + "deny": { + "title": "Denegar tot" + } + }, + "edit-batch": { + "approve": { + "title": "Desar tot" + }, + "deny": { + "title": "Denegar tot" + } + }, "indexingStatus": { "ready": "Índex preparat", "indexing": "Indexant {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/de/chat.json b/webview-ui/src/i18n/locales/de/chat.json index e0f93dfe327..082fc967ebd 100644 --- a/webview-ui/src/i18n/locales/de/chat.json +++ b/webview-ui/src/i18n/locales/de/chat.json @@ -440,6 +440,22 @@ "title": "Alle ablehnen" } }, + "list-batch": { + "approve": { + "title": "Alle genehmigen" + }, + "deny": { + "title": "Alle ablehnen" + } + }, + "edit-batch": { + "approve": { + "title": "Alle speichern" + }, + "deny": { + "title": "Alle ablehnen" + } + }, "indexingStatus": { "ready": "Index bereit", "indexing": "Indizierung {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/es/chat.json b/webview-ui/src/i18n/locales/es/chat.json index b7329cdab1b..a3a66bf7d5f 100644 --- a/webview-ui/src/i18n/locales/es/chat.json +++ b/webview-ui/src/i18n/locales/es/chat.json @@ -440,6 +440,22 @@ "title": "Denegar todo" } }, + "list-batch": { + "approve": { + "title": "Aprobar todo" + }, + "deny": { + "title": "Denegar todo" + } + }, + "edit-batch": { + "approve": { + "title": "Guardar todo" + }, + "deny": { + "title": "Denegar todo" + } + }, "indexingStatus": { "ready": "Índice listo", "indexing": "Indexando {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/fr/chat.json b/webview-ui/src/i18n/locales/fr/chat.json index 74283e7d4a1..1dfae130ac1 100644 --- a/webview-ui/src/i18n/locales/fr/chat.json +++ b/webview-ui/src/i18n/locales/fr/chat.json @@ -440,6 +440,22 @@ "title": "Tout refuser" } }, + "list-batch": { + "approve": { + "title": "Tout approuver" + }, + "deny": { + "title": "Tout refuser" + } + }, + "edit-batch": { + "approve": { + "title": "Tout enregistrer" + }, + "deny": { + "title": "Tout refuser" + } + }, "indexingStatus": { "ready": "Index prêt", "indexing": "Indexation {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/hi/chat.json b/webview-ui/src/i18n/locales/hi/chat.json index a6c7683c849..bac3221048e 100644 --- a/webview-ui/src/i18n/locales/hi/chat.json +++ b/webview-ui/src/i18n/locales/hi/chat.json @@ -440,6 +440,22 @@ "title": "सभी अस्वीकार करें" } }, + "list-batch": { + "approve": { + "title": "सभी स्वीकृत करें" + }, + "deny": { + "title": "सभी अस्वीकार करें" + } + }, + "edit-batch": { + "approve": { + "title": "सभी सहेजें" + }, + "deny": { + "title": "सभी अस्वीकार करें" + } + }, "indexingStatus": { "ready": "इंडेक्स तैयार", "indexing": "इंडेक्सिंग {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/id/chat.json b/webview-ui/src/i18n/locales/id/chat.json index 4dfc357bf24..4e220d2666a 100644 --- a/webview-ui/src/i18n/locales/id/chat.json +++ b/webview-ui/src/i18n/locales/id/chat.json @@ -80,6 +80,22 @@ "title": "Tolak Semua" } }, + "list-batch": { + "approve": { + "title": "Setujui Semua" + }, + "deny": { + "title": "Tolak Semua" + } + }, + "edit-batch": { + "approve": { + "title": "Simpan Semua" + }, + "deny": { + "title": "Tolak Semua" + } + }, "runCommand": { "title": "Perintah", "tooltip": "Jalankan perintah ini" diff --git a/webview-ui/src/i18n/locales/it/chat.json b/webview-ui/src/i18n/locales/it/chat.json index 8aaa2ebac3a..e77a7f32851 100644 --- a/webview-ui/src/i18n/locales/it/chat.json +++ b/webview-ui/src/i18n/locales/it/chat.json @@ -440,6 +440,22 @@ "title": "Nega tutto" } }, + "list-batch": { + "approve": { + "title": "Approva tutto" + }, + "deny": { + "title": "Nega tutto" + } + }, + "edit-batch": { + "approve": { + "title": "Salva tutto" + }, + "deny": { + "title": "Nega tutto" + } + }, "indexingStatus": { "ready": "Indice pronto", "indexing": "Indicizzazione {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/ja/chat.json b/webview-ui/src/i18n/locales/ja/chat.json index 48b13cfd66d..f24f528045a 100644 --- a/webview-ui/src/i18n/locales/ja/chat.json +++ b/webview-ui/src/i18n/locales/ja/chat.json @@ -440,6 +440,22 @@ "title": "すべて拒否" } }, + "list-batch": { + "approve": { + "title": "すべて承認" + }, + "deny": { + "title": "すべて拒否" + } + }, + "edit-batch": { + "approve": { + "title": "すべて保存" + }, + "deny": { + "title": "すべて拒否" + } + }, "indexingStatus": { "ready": "インデックス準備完了", "indexing": "インデックス作成中 {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/ko/chat.json b/webview-ui/src/i18n/locales/ko/chat.json index eb037eefe16..f3e59d8defe 100644 --- a/webview-ui/src/i18n/locales/ko/chat.json +++ b/webview-ui/src/i18n/locales/ko/chat.json @@ -440,6 +440,22 @@ "title": "모두 거부" } }, + "list-batch": { + "approve": { + "title": "모두 승인" + }, + "deny": { + "title": "모두 거부" + } + }, + "edit-batch": { + "approve": { + "title": "모두 저장" + }, + "deny": { + "title": "모두 거부" + } + }, "indexingStatus": { "ready": "인덱스 준비됨", "indexing": "인덱싱 중 {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/nl/chat.json b/webview-ui/src/i18n/locales/nl/chat.json index 77d25310008..f1629955336 100644 --- a/webview-ui/src/i18n/locales/nl/chat.json +++ b/webview-ui/src/i18n/locales/nl/chat.json @@ -440,6 +440,22 @@ "title": "Alles weigeren" } }, + "list-batch": { + "approve": { + "title": "Alles goedkeuren" + }, + "deny": { + "title": "Alles weigeren" + } + }, + "edit-batch": { + "approve": { + "title": "Alles opslaan" + }, + "deny": { + "title": "Alles weigeren" + } + }, "indexingStatus": { "ready": "Index gereed", "indexing": "Indexeren {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/pl/chat.json b/webview-ui/src/i18n/locales/pl/chat.json index 66801899e9c..8bd955ee944 100644 --- a/webview-ui/src/i18n/locales/pl/chat.json +++ b/webview-ui/src/i18n/locales/pl/chat.json @@ -440,6 +440,22 @@ "title": "Odrzuć wszystko" } }, + "list-batch": { + "approve": { + "title": "Zatwierdź wszystko" + }, + "deny": { + "title": "Odrzuć wszystko" + } + }, + "edit-batch": { + "approve": { + "title": "Zapisz wszystko" + }, + "deny": { + "title": "Odrzuć wszystko" + } + }, "indexingStatus": { "ready": "Indeks gotowy", "indexing": "Indeksowanie {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/pt-BR/chat.json b/webview-ui/src/i18n/locales/pt-BR/chat.json index d563aa6b367..01f23569300 100644 --- a/webview-ui/src/i18n/locales/pt-BR/chat.json +++ b/webview-ui/src/i18n/locales/pt-BR/chat.json @@ -440,6 +440,22 @@ "title": "Negar tudo" } }, + "list-batch": { + "approve": { + "title": "Aprovar tudo" + }, + "deny": { + "title": "Negar tudo" + } + }, + "edit-batch": { + "approve": { + "title": "Salvar tudo" + }, + "deny": { + "title": "Negar tudo" + } + }, "indexingStatus": { "ready": "Índice pronto", "indexing": "Indexando {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/ru/chat.json b/webview-ui/src/i18n/locales/ru/chat.json index f599bacaa16..1f2142ea95e 100644 --- a/webview-ui/src/i18n/locales/ru/chat.json +++ b/webview-ui/src/i18n/locales/ru/chat.json @@ -441,6 +441,22 @@ "title": "Отклонить все" } }, + "list-batch": { + "approve": { + "title": "Одобрить все" + }, + "deny": { + "title": "Отклонить все" + } + }, + "edit-batch": { + "approve": { + "title": "Сохранить все" + }, + "deny": { + "title": "Отклонить все" + } + }, "indexingStatus": { "ready": "Индекс готов", "indexing": "Индексация {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/tr/chat.json b/webview-ui/src/i18n/locales/tr/chat.json index e4a9bf71f00..f7a2d951f1f 100644 --- a/webview-ui/src/i18n/locales/tr/chat.json +++ b/webview-ui/src/i18n/locales/tr/chat.json @@ -441,6 +441,22 @@ "title": "Tümünü Reddet" } }, + "list-batch": { + "approve": { + "title": "Tümünü Onayla" + }, + "deny": { + "title": "Tümünü Reddet" + } + }, + "edit-batch": { + "approve": { + "title": "Tümünü Kaydet" + }, + "deny": { + "title": "Tümünü Reddet" + } + }, "indexingStatus": { "ready": "İndeks hazır", "indexing": "İndeksleniyor {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/vi/chat.json b/webview-ui/src/i18n/locales/vi/chat.json index 7823801766c..a2c1389e41f 100644 --- a/webview-ui/src/i18n/locales/vi/chat.json +++ b/webview-ui/src/i18n/locales/vi/chat.json @@ -441,6 +441,22 @@ "title": "Từ chối tất cả" } }, + "list-batch": { + "approve": { + "title": "Chấp nhận tất cả" + }, + "deny": { + "title": "Từ chối tất cả" + } + }, + "edit-batch": { + "approve": { + "title": "Lưu tất cả" + }, + "deny": { + "title": "Từ chối tất cả" + } + }, "indexingStatus": { "ready": "Chỉ mục sẵn sàng", "indexing": "Đang lập chỉ mục {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/zh-CN/chat.json b/webview-ui/src/i18n/locales/zh-CN/chat.json index 4e5002b33d5..29783285de5 100644 --- a/webview-ui/src/i18n/locales/zh-CN/chat.json +++ b/webview-ui/src/i18n/locales/zh-CN/chat.json @@ -441,6 +441,22 @@ "title": "全部拒绝" } }, + "list-batch": { + "approve": { + "title": "全部批准" + }, + "deny": { + "title": "全部拒绝" + } + }, + "edit-batch": { + "approve": { + "title": "全部保存" + }, + "deny": { + "title": "全部拒绝" + } + }, "indexingStatus": { "ready": "索引就绪", "indexing": "索引中 {{percentage}}%", diff --git a/webview-ui/src/i18n/locales/zh-TW/chat.json b/webview-ui/src/i18n/locales/zh-TW/chat.json index 49b459d54b7..8cb612dcd22 100644 --- a/webview-ui/src/i18n/locales/zh-TW/chat.json +++ b/webview-ui/src/i18n/locales/zh-TW/chat.json @@ -74,6 +74,22 @@ "title": "全部拒絕" } }, + "list-batch": { + "approve": { + "title": "全部核准" + }, + "deny": { + "title": "全部拒絕" + } + }, + "edit-batch": { + "approve": { + "title": "全部儲存" + }, + "deny": { + "title": "全部拒絕" + } + }, "runCommand": { "title": "執行", "tooltip": "執行此命令"