Преглед на файлове

ux: add downloadable error diagnostics from chat errors (#10188)

Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com>
Co-authored-by: daniel-lxs <[email protected]>
Co-authored-by: Chris Estreich <[email protected]>
Bruno Bergher преди 2 месеца
родител
ревизия
bb358fb8f9

+ 188 - 0
src/core/webview/__tests__/diagnosticsHandler.spec.ts

@@ -0,0 +1,188 @@
+// npx vitest src/core/webview/__tests__/diagnosticsHandler.spec.ts
+
+import * as path from "path"
+
+// Mock vscode first
+vi.mock("vscode", () => {
+	const showErrorMessage = vi.fn()
+	const openTextDocument = vi.fn().mockResolvedValue({})
+	const showTextDocument = vi.fn().mockResolvedValue(undefined)
+
+	return {
+		window: {
+			showErrorMessage,
+			showTextDocument,
+		},
+		workspace: {
+			openTextDocument,
+		},
+	}
+})
+
+// Mock storage utilities
+vi.mock("../../../utils/storage", () => ({
+	getTaskDirectoryPath: vi.fn(async () => "/mock/task-dir"),
+}))
+
+// Mock fs utilities
+vi.mock("../../../utils/fs", () => ({
+	fileExistsAtPath: vi.fn(),
+}))
+
+// Mock fs/promises
+vi.mock("fs/promises", () => {
+	const mockReadFile = vi.fn()
+	const mockWriteFile = vi.fn().mockResolvedValue(undefined)
+
+	return {
+		default: {
+			readFile: mockReadFile,
+			writeFile: mockWriteFile,
+		},
+		readFile: mockReadFile,
+		writeFile: mockWriteFile,
+	}
+})
+
+import * as vscode from "vscode"
+import * as fs from "fs/promises"
+import * as fsUtils from "../../../utils/fs"
+import { generateErrorDiagnostics } from "../diagnosticsHandler"
+
+describe("generateErrorDiagnostics", () => {
+	const mockLog = vi.fn()
+
+	beforeEach(() => {
+		vi.clearAllMocks()
+	})
+
+	it("generates a diagnostics file with error metadata and history", async () => {
+		vi.mocked(fsUtils.fileExistsAtPath).mockResolvedValue(true as any)
+		vi.mocked(fs.readFile).mockResolvedValue('[{"role": "user", "content": "test"}]' as any)
+
+		const result = await generateErrorDiagnostics({
+			taskId: "test-task-id",
+			globalStoragePath: "/mock/global/storage",
+			values: {
+				timestamp: "2025-01-01T00:00:00.000Z",
+				version: "1.2.3",
+				provider: "test-provider",
+				model: "test-model",
+				details: "Sample error details",
+			},
+			log: mockLog,
+		})
+
+		expect(result.success).toBe(true)
+		expect(result.filePath).toContain("roo-diagnostics-")
+
+		// Verify we attempted to read API history
+		expect(fs.readFile).toHaveBeenCalledWith(path.join("/mock/task-dir", "api_conversation_history.json"), "utf8")
+
+		// Verify we wrote a diagnostics file with the expected content
+		expect(fs.writeFile).toHaveBeenCalledTimes(1)
+		const [writtenPath, writtenContent] = vi.mocked(fs.writeFile).mock.calls[0]
+		// taskId.slice(0, 8) = "test-tas" from "test-task-id"
+		expect(String(writtenPath)).toContain("roo-diagnostics-test-tas")
+		expect(String(writtenContent)).toContain(
+			"// Please share this file with Roo Code Support ([email protected]) to diagnose the issue faster",
+		)
+		expect(String(writtenContent)).toContain('"error":')
+		expect(String(writtenContent)).toContain('"history":')
+		expect(String(writtenContent)).toContain('"version": "1.2.3"')
+		expect(String(writtenContent)).toContain('"provider": "test-provider"')
+		expect(String(writtenContent)).toContain('"model": "test-model"')
+		expect(String(writtenContent)).toContain('"details": "Sample error details"')
+
+		// Verify VS Code APIs were used to open the generated file
+		expect(vscode.workspace.openTextDocument).toHaveBeenCalledTimes(1)
+		expect(vscode.window.showTextDocument).toHaveBeenCalledTimes(1)
+	})
+
+	it("uses empty history when API history file does not exist", async () => {
+		vi.mocked(fsUtils.fileExistsAtPath).mockResolvedValue(false as any)
+
+		const result = await generateErrorDiagnostics({
+			taskId: "test-task-id",
+			globalStoragePath: "/mock/global/storage",
+			values: {
+				timestamp: "2025-01-01T00:00:00.000Z",
+				version: "1.0.0",
+				provider: "test",
+				model: "test",
+				details: "error",
+			},
+			log: mockLog,
+		})
+
+		expect(result.success).toBe(true)
+
+		// Should not attempt to read file when it doesn't exist
+		expect(fs.readFile).not.toHaveBeenCalled()
+
+		// Verify empty history in output
+		const [, writtenContent] = vi.mocked(fs.writeFile).mock.calls[0]
+		expect(String(writtenContent)).toContain('"history": []')
+	})
+
+	it("uses default values when values are not provided", async () => {
+		vi.mocked(fsUtils.fileExistsAtPath).mockResolvedValue(false as any)
+
+		const result = await generateErrorDiagnostics({
+			taskId: "test-task-id",
+			globalStoragePath: "/mock/global/storage",
+			log: mockLog,
+		})
+
+		expect(result.success).toBe(true)
+
+		// Verify defaults in output
+		const [, writtenContent] = vi.mocked(fs.writeFile).mock.calls[0]
+		expect(String(writtenContent)).toContain('"version": ""')
+		expect(String(writtenContent)).toContain('"provider": ""')
+		expect(String(writtenContent)).toContain('"model": ""')
+		expect(String(writtenContent)).toContain('"details": ""')
+	})
+
+	it("handles JSON parse error gracefully", async () => {
+		vi.mocked(fsUtils.fileExistsAtPath).mockResolvedValue(true as any)
+		vi.mocked(fs.readFile).mockResolvedValue("invalid json" as any)
+
+		const result = await generateErrorDiagnostics({
+			taskId: "test-task-id",
+			globalStoragePath: "/mock/global/storage",
+			values: {
+				timestamp: "2025-01-01T00:00:00.000Z",
+				version: "1.0.0",
+				provider: "test",
+				model: "test",
+				details: "error",
+			},
+			log: mockLog,
+		})
+
+		// Should still succeed but with empty history
+		expect(result.success).toBe(true)
+		expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Failed to parse api_conversation_history.json")
+
+		// Verify empty history in output
+		const [, writtenContent] = vi.mocked(fs.writeFile).mock.calls[0]
+		expect(String(writtenContent)).toContain('"history": []')
+	})
+
+	it("returns error result when file write fails", async () => {
+		vi.mocked(fsUtils.fileExistsAtPath).mockResolvedValue(false as any)
+		vi.mocked(fs.writeFile).mockRejectedValue(new Error("Write failed"))
+
+		const result = await generateErrorDiagnostics({
+			taskId: "test-task-id",
+			globalStoragePath: "/mock/global/storage",
+			log: mockLog,
+		})
+
+		expect(result.success).toBe(false)
+		expect(result.error).toBe("Write failed")
+		expect(mockLog).toHaveBeenCalledWith("Error generating diagnostics: Write failed")
+		expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("Failed to generate diagnostics: Write failed")
+	})
+})

+ 84 - 9
src/core/webview/__tests__/webviewMessageHandler.spec.ts

@@ -5,6 +5,11 @@ import type { Mock } from "vitest"
 // Mock dependencies - must come before imports
 vi.mock("../../../api/providers/fetchers/modelCache")
 
+// Mock the diagnosticsHandler module
+vi.mock("../diagnosticsHandler", () => ({
+	generateErrorDiagnostics: vi.fn().mockResolvedValue({ success: true, filePath: "/tmp/diagnostics.json" }),
+}))
+
 import { webviewMessageHandler } from "../webviewMessageHandler"
 import type { ClineProvider } from "../ClineProvider"
 import { getModels } from "../../../api/providers/fetchers/modelCache"
@@ -41,15 +46,24 @@ const mockClineProvider = {
 
 import { t } from "../../../i18n"
 
-vi.mock("vscode", () => ({
-	window: {
-		showInformationMessage: vi.fn(),
-		showErrorMessage: vi.fn(),
-	},
-	workspace: {
-		workspaceFolders: [{ uri: { fsPath: "/mock/workspace" } }],
-	},
-}))
+vi.mock("vscode", () => {
+	const showInformationMessage = vi.fn()
+	const showErrorMessage = vi.fn()
+	const openTextDocument = vi.fn().mockResolvedValue({})
+	const showTextDocument = vi.fn().mockResolvedValue(undefined)
+
+	return {
+		window: {
+			showInformationMessage,
+			showErrorMessage,
+			showTextDocument,
+		},
+		workspace: {
+			workspaceFolders: [{ uri: { fsPath: "/mock/workspace" } }],
+			openTextDocument,
+		},
+	}
+})
 
 vi.mock("../../../i18n", () => ({
 	t: vi.fn((key: string, args?: Record<string, any>) => {
@@ -72,14 +86,20 @@ vi.mock("../../../i18n", () => ({
 vi.mock("fs/promises", () => {
 	const mockRm = vi.fn().mockResolvedValue(undefined)
 	const mockMkdir = vi.fn().mockResolvedValue(undefined)
+	const mockReadFile = vi.fn().mockResolvedValue("[]")
+	const mockWriteFile = vi.fn().mockResolvedValue(undefined)
 
 	return {
 		default: {
 			rm: mockRm,
 			mkdir: mockMkdir,
+			readFile: mockReadFile,
+			writeFile: mockWriteFile,
 		},
 		rm: mockRm,
 		mkdir: mockMkdir,
+		readFile: mockReadFile,
+		writeFile: mockWriteFile,
 	}
 })
 
@@ -90,6 +110,7 @@ import * as path from "path"
 import * as fsUtils from "../../../utils/fs"
 import { getWorkspacePath } from "../../../utils/path"
 import { ensureSettingsDirectoryExists } from "../../../utils/globalContext"
+import { generateErrorDiagnostics } from "../diagnosticsHandler"
 import type { ModeConfig } from "@roo-code/types"
 
 vi.mock("../../../utils/fs")
@@ -739,3 +760,57 @@ describe("webviewMessageHandler - mcpEnabled", () => {
 		expect(mockClineProvider.postStateToWebview).toHaveBeenCalledTimes(1)
 	})
 })
+
+describe("webviewMessageHandler - downloadErrorDiagnostics", () => {
+	beforeEach(() => {
+		vi.clearAllMocks()
+
+		// Ensure contextProxy has a globalStorageUri for the handler
+		;(mockClineProvider as any).contextProxy.globalStorageUri = { fsPath: "/mock/global/storage" }
+
+		// Provide a current task with a stable ID
+		vi.mocked(mockClineProvider.getCurrentTask).mockReturnValue({
+			taskId: "test-task-id",
+		} as any)
+	})
+
+	it("calls generateErrorDiagnostics with correct parameters", async () => {
+		await webviewMessageHandler(mockClineProvider, {
+			type: "downloadErrorDiagnostics",
+			values: {
+				timestamp: "2025-01-01T00:00:00.000Z",
+				version: "1.2.3",
+				provider: "test-provider",
+				model: "test-model",
+				details: "Sample error details",
+			},
+		} as any)
+
+		// Verify generateErrorDiagnostics was called with the correct parameters
+		expect(generateErrorDiagnostics).toHaveBeenCalledTimes(1)
+		expect(generateErrorDiagnostics).toHaveBeenCalledWith({
+			taskId: "test-task-id",
+			globalStoragePath: "/mock/global/storage",
+			values: {
+				timestamp: "2025-01-01T00:00:00.000Z",
+				version: "1.2.3",
+				provider: "test-provider",
+				model: "test-model",
+				details: "Sample error details",
+			},
+			log: expect.any(Function),
+		})
+	})
+
+	it("shows error when no active task", async () => {
+		vi.mocked(mockClineProvider.getCurrentTask).mockReturnValue(null as any)
+
+		await webviewMessageHandler(mockClineProvider, {
+			type: "downloadErrorDiagnostics",
+			values: {},
+		} as any)
+
+		expect(vscode.window.showErrorMessage).toHaveBeenCalledWith("No active task to generate diagnostics for")
+		expect(generateErrorDiagnostics).not.toHaveBeenCalled()
+	})
+})

+ 92 - 0
src/core/webview/diagnosticsHandler.ts

@@ -0,0 +1,92 @@
+import * as path from "path"
+import * as os from "os"
+import * as fs from "fs/promises"
+import * as vscode from "vscode"
+
+import { getTaskDirectoryPath } from "../../utils/storage"
+import { fileExistsAtPath } from "../../utils/fs"
+
+export interface ErrorDiagnosticsValues {
+	timestamp?: string
+	version?: string
+	provider?: string
+	model?: string
+	details?: string
+}
+
+export interface GenerateDiagnosticsParams {
+	taskId: string
+	globalStoragePath: string
+	values?: ErrorDiagnosticsValues
+	log: (message: string) => void
+}
+
+export interface GenerateDiagnosticsResult {
+	success: boolean
+	filePath?: string
+	error?: string
+}
+
+/**
+ * Generates an error diagnostics file containing error metadata and API conversation history.
+ * The file is created in the system temp directory and opened in VS Code for the user to review
+ * before sharing with support.
+ */
+export async function generateErrorDiagnostics(params: GenerateDiagnosticsParams): Promise<GenerateDiagnosticsResult> {
+	const { taskId, globalStoragePath, values, log } = params
+
+	try {
+		const taskDirPath = await getTaskDirectoryPath(globalStoragePath, taskId)
+
+		// Load API conversation history from the same file used by openDebugApiHistory
+		const apiHistoryPath = path.join(taskDirPath, "api_conversation_history.json")
+		let history: unknown = []
+
+		if (await fileExistsAtPath(apiHistoryPath)) {
+			const content = await fs.readFile(apiHistoryPath, "utf8")
+			try {
+				history = JSON.parse(content)
+			} catch {
+				// If parsing fails, fall back to empty history but still generate diagnostics file
+				vscode.window.showErrorMessage("Failed to parse api_conversation_history.json")
+			}
+		}
+
+		const diagnostics = {
+			error: {
+				timestamp: values?.timestamp ?? new Date().toISOString(),
+				version: values?.version ?? "",
+				provider: values?.provider ?? "",
+				model: values?.model ?? "",
+				details: values?.details ?? "",
+			},
+			history,
+		}
+
+		// Prepend human-readable guidance comments before the JSON payload
+		const headerComment =
+			"// Please share this file with Roo Code Support ([email protected]) to diagnose the issue faster\n" +
+			"// Just make sure you're OK sharing the contents of the conversation below.\n\n"
+		const jsonContent = JSON.stringify(diagnostics, null, 2)
+		const fullContent = headerComment + jsonContent
+
+		// Create a temporary diagnostics file
+		const tmpDir = os.tmpdir()
+		const timestamp = Date.now()
+		const tempFileName = `roo-diagnostics-${taskId.slice(0, 8)}-${timestamp}.json`
+		const tempFilePath = path.join(tmpDir, tempFileName)
+
+		await fs.writeFile(tempFilePath, fullContent, "utf8")
+
+		// Open the diagnostics file in VS Code
+		const doc = await vscode.workspace.openTextDocument(tempFilePath)
+		await vscode.window.showTextDocument(doc, { preview: true })
+
+		return { success: true, filePath: tempFilePath }
+	} catch (error) {
+		const errorMessage = error instanceof Error ? error.message : String(error)
+		log(`Error generating diagnostics: ${errorMessage}`)
+		vscode.window.showErrorMessage(`Failed to generate diagnostics: ${errorMessage}`)
+		return { success: false, error: errorMessage }
+	}
+}

+ 17 - 0
src/core/webview/webviewMessageHandler.ts

@@ -25,6 +25,7 @@ import { saveTaskMessages } from "../task-persistence"
 import { ClineProvider } from "./ClineProvider"
 import { BrowserSessionPanelManager } from "./BrowserSessionPanelManager"
 import { handleCheckpointRestoreOperation } from "./checkpointRestoreHandler"
+import { generateErrorDiagnostics } from "./diagnosticsHandler"
 import { changeLanguage, t } from "../../i18n"
 import { Package } from "../../shared/package"
 import { type RouterName, type ModelRecord, toRouterName } from "../../shared/api"
@@ -3173,6 +3174,22 @@ export const webviewMessageHandler = async (
 			break
 		}
 
+		case "downloadErrorDiagnostics": {
+			const currentTask = provider.getCurrentTask()
+			if (!currentTask) {
+				vscode.window.showErrorMessage("No active task to generate diagnostics for")
+				break
+			}
+
+			await generateErrorDiagnostics({
+				taskId: currentTask.taskId,
+				globalStoragePath: provider.contextProxy.globalStorageUri.fsPath,
+				values: message.values,
+				log: (msg) => provider.log(msg),
+			})
+			break
+		}
+
 		default: {
 			// console.log(`Unhandled message type: ${message.type}`)
 			//

+ 1 - 0
src/shared/WebviewMessage.ts

@@ -178,6 +178,7 @@ export interface WebviewMessage {
 		| "browserPanelDidLaunch"
 		| "openDebugApiHistory"
 		| "openDebugUiHistory"
+		| "downloadErrorDiagnostics"
 		| "requestClaudeCodeRateLimits"
 	text?: string
 	editedMessageContent?: string

+ 24 - 2
webview-ui/src/components/chat/ErrorRow.tsx

@@ -1,7 +1,8 @@
 import React, { useState, useCallback, memo, useMemo } from "react"
 import { useTranslation } from "react-i18next"
 import { VSCodeButton } from "@vscode/webview-ui-toolkit/react"
-import { BookOpenText, MessageCircleWarning, Copy, Check } from "lucide-react"
+import { BookOpenText, MessageCircleWarning, Copy, Check, Microscope } from "lucide-react"
+
 import { useCopyToClipboard } from "@src/utils/clipboard"
 import { vscode } from "@src/utils/vscode"
 import CodeBlock from "../common/CodeBlock"
@@ -112,6 +113,23 @@ export const ErrorRow = memo(
 			return metadata + errorDetails
 		}, [errorDetails, version, provider, modelId])
 
+		const handleDownloadDiagnostics = useCallback(
+			(e: React.MouseEvent) => {
+				e.stopPropagation()
+				vscode.postMessage({
+					type: "downloadErrorDiagnostics",
+					values: {
+						timestamp: new Date().toISOString(),
+						version,
+						provider,
+						model: modelId,
+						details: errorDetails || "",
+					},
+				})
+			},
+			[version, provider, modelId, errorDetails],
+		)
+
 		// Default titles for different error types
 		const getDefaultTitle = () => {
 			if (title) return title
@@ -278,7 +296,7 @@ export const ErrorRow = memo(
 								</pre>
 							</div>
 							<DialogFooter>
-								<Button variant="secondary" onClick={handleCopyDetails}>
+								<Button variant="secondary" className="w-full" onClick={handleCopyDetails}>
 									{showDetailsCopySuccess ? (
 										<>
 											<Check className="size-3" />
@@ -291,6 +309,10 @@ export const ErrorRow = memo(
 										</>
 									)}
 								</Button>
+								<Button variant="secondary" className="w-full" onClick={handleDownloadDiagnostics}>
+									<Microscope className="size-3" />
+									{t("chat:errorDetails.diagnostics")}
+								</Button>
 							</DialogFooter>
 						</DialogContent>
 					</Dialog>

+ 82 - 0
webview-ui/src/components/chat/__tests__/ErrorRow.spec.tsx

@@ -0,0 +1,82 @@
+import React from "react"
+
+import { render, screen, fireEvent } from "@/utils/test-utils"
+import { vscode } from "@/utils/vscode"
+
+import { ErrorRow } from "../ErrorRow"
+
+// Mock vscode webview messaging
+vi.mock("@/utils/vscode", () => ({
+	vscode: {
+		postMessage: vi.fn(),
+	},
+}))
+
+// Mock ExtensionState context
+vi.mock("@/context/ExtensionStateContext", () => ({
+	useExtensionState: () => ({
+		version: "1.0.0",
+		apiConfiguration: {},
+	}),
+}))
+
+// Mock selected model hook
+vi.mock("@/components/ui/hooks/useSelectedModel", () => ({
+	useSelectedModel: () => ({
+		provider: "test-provider",
+		id: "test-model",
+	}),
+}))
+
+// Mock i18n
+vi.mock("react-i18next", () => ({
+	useTranslation: () => ({
+		t: (key: string) => {
+			const map: Record<string, string> = {
+				"chat:error": "Error",
+				"chat:errorDetails.title": "Error Details",
+				"chat:errorDetails.copyToClipboard": "Copy to Clipboard",
+				"chat:errorDetails.copied": "Copied!",
+				"chat:errorDetails.diagnostics": "Get detailed error info",
+			}
+			return map[key] ?? key
+		},
+	}),
+	initReactI18next: {
+		type: "3rdParty",
+		init: vi.fn(),
+	},
+}))
+
+describe("ErrorRow diagnostics download", () => {
+	it("sends downloadErrorDiagnostics message with error metadata", () => {
+		const mockPostMessage = vi.mocked(vscode.postMessage)
+
+		render(<ErrorRow type="error" message="Something went wrong" errorDetails="Detailed error body" />)
+
+		// Open the Error Details dialog via the info button
+		const infoButton = screen.getByRole("button", { name: "Error Details" })
+		fireEvent.click(infoButton)
+
+		// Click the diagnostics button
+		const downloadButton = screen.getByRole("button", { name: "Get detailed error info" })
+		fireEvent.click(downloadButton)
+
+		expect(mockPostMessage).toHaveBeenCalled()
+		const call = mockPostMessage.mock.calls.find(([arg]) => arg.type === "downloadErrorDiagnostics")
+		expect(call).toBeTruthy()
+		if (!call) return
+
+		const payload = call[0] as { type: string; values?: any }
+		expect(payload.values).toBeTruthy()
+		if (!payload.values) return
+
+		expect(payload.values).toMatchObject({
+			version: "1.0.0",
+			provider: "test-provider",
+			model: "test-model",
+		})
+		// Timestamp is generated at runtime, but should be a string
+		expect(typeof payload.values.timestamp).toBe("string")
+	})
+})

+ 2 - 1
webview-ui/src/i18n/locales/ca/chat.json

@@ -267,7 +267,8 @@
 		"link": "Details",
 		"title": "Detalls de l'error",
 		"copyToClipboard": "Copiar al porta-retalls",
-		"copied": "Copiat!"
+		"copied": "Copiat!",
+		"diagnostics": "Obtenir informació d'error detallada"
 	},
 	"powershell": {
 		"issues": "Sembla que estàs tenint problemes amb Windows PowerShell, si us plau consulta aquesta documentació per a més informació."

+ 2 - 1
webview-ui/src/i18n/locales/de/chat.json

@@ -267,7 +267,8 @@
 		"link": "Details",
 		"title": "Fehlerdetails",
 		"copyToClipboard": "In Zwischenablage kopieren",
-		"copied": "Kopiert!"
+		"copied": "Kopiert!",
+		"diagnostics": "Detaillierte Fehlerinformationen abrufen"
 	},
 	"powershell": {
 		"issues": "Es scheint, dass du Probleme mit Windows PowerShell hast, bitte sieh dir dies an"

+ 3 - 2
webview-ui/src/i18n/locales/en/chat.json

@@ -293,8 +293,9 @@
 	"errorDetails": {
 		"link": "Details",
 		"title": "Error Details",
-		"copyToClipboard": "Copy to Clipboard",
-		"copied": "Copied!"
+		"copyToClipboard": "Copy basic error info",
+		"copied": "Copied!",
+		"diagnostics": "Get detailed error info"
 	},
 	"diffError": {
 		"title": "Edit Unsuccessful"

+ 2 - 1
webview-ui/src/i18n/locales/es/chat.json

@@ -267,7 +267,8 @@
 		"link": "Detalles",
 		"title": "Detalles del error",
 		"copyToClipboard": "Copiar al portapapeles",
-		"copied": "¡Copiado!"
+		"copied": "¡Copiado!",
+		"diagnostics": "Obtener información de error detallada"
 	},
 	"powershell": {
 		"issues": "Parece que estás teniendo problemas con Windows PowerShell, por favor consulta esta"

+ 2 - 1
webview-ui/src/i18n/locales/fr/chat.json

@@ -267,7 +267,8 @@
 		"link": "Détails",
 		"title": "Détails de l'erreur",
 		"copyToClipboard": "Copier dans le presse-papiers",
-		"copied": "Copié !"
+		"copied": "Copié !",
+		"diagnostics": "Obtenir les détails de l'erreur"
 	},
 	"powershell": {
 		"issues": "Il semble que vous rencontriez des problèmes avec Windows PowerShell, veuillez consulter ce"

+ 2 - 1
webview-ui/src/i18n/locales/hi/chat.json

@@ -267,7 +267,8 @@
 		"link": "विवरण",
 		"title": "त्रुटि विवरण",
 		"copyToClipboard": "क्लिपबोर्ड पर कॉपी करें",
-		"copied": "कॉपी किया गया!"
+		"copied": "कॉपी किया गया!",
+		"diagnostics": "विस्तृत त्रुटि जानकारी प्राप्त करें"
 	},
 	"powershell": {
 		"issues": "ऐसा लगता है कि आपको Windows PowerShell के साथ समस्याएँ हो रही हैं, कृपया इसे देखें"

+ 2 - 1
webview-ui/src/i18n/locales/id/chat.json

@@ -303,7 +303,8 @@
 		"link": "Details",
 		"title": "Detail Kesalahan",
 		"copyToClipboard": "Salin ke Clipboard",
-		"copied": "Disalin!"
+		"copied": "Disalin!",
+		"diagnostics": "Dapatkan informasi kesalahan terperinci"
 	},
 	"powershell": {
 		"issues": "Sepertinya kamu mengalami masalah Windows PowerShell, silakan lihat ini"

+ 2 - 1
webview-ui/src/i18n/locales/it/chat.json

@@ -267,7 +267,8 @@
 		"link": "Dettagli",
 		"title": "Dettagli errore",
 		"copyToClipboard": "Copia negli appunti",
-		"copied": "Copiato!"
+		"copied": "Copiato!",
+		"diagnostics": "Ottieni informazioni errore dettagliate"
 	},
 	"powershell": {
 		"issues": "Sembra che tu stia avendo problemi con Windows PowerShell, consulta questa"

+ 2 - 1
webview-ui/src/i18n/locales/ja/chat.json

@@ -267,7 +267,8 @@
 		"link": "詳細",
 		"title": "エラー詳細",
 		"copyToClipboard": "クリップボードにコピー",
-		"copied": "コピーしました!"
+		"copied": "コピーしました!",
+		"diagnostics": "詳細なエラー情報を取得"
 	},
 	"powershell": {
 		"issues": "Windows PowerShellに問題があるようです。こちらを参照してください"

+ 2 - 1
webview-ui/src/i18n/locales/ko/chat.json

@@ -267,7 +267,8 @@
 		"link": "자세히",
 		"title": "오류 세부 정보",
 		"copyToClipboard": "클립보드에 복사",
-		"copied": "복사됨!"
+		"copied": "복사됨!",
+		"diagnostics": "상세한 오류 정보 가져오기"
 	},
 	"powershell": {
 		"issues": "Windows PowerShell에 문제가 있는 것 같습니다. 다음을 참조하세요"

+ 2 - 1
webview-ui/src/i18n/locales/nl/chat.json

@@ -267,7 +267,8 @@
 		"link": "Details",
 		"title": "Foutdetails",
 		"copyToClipboard": "Naar klembord kopiëren",
-		"copied": "Gekopieerd!"
+		"copied": "Gekopieerd!",
+		"diagnostics": "Gedetailleerde foutinformatie ophalen"
 	},
 	"powershell": {
 		"issues": "Het lijkt erop dat je problemen hebt met Windows PowerShell, zie deze"

+ 2 - 1
webview-ui/src/i18n/locales/pl/chat.json

@@ -267,7 +267,8 @@
 		"link": "Details",
 		"title": "Szczegóły błędu",
 		"copyToClipboard": "Kopiuj do schowka",
-		"copied": "Skopiowano!"
+		"copied": "Skopiowano!",
+		"diagnostics": "Uzyskaj szczegółowe informacje o błędzie"
 	},
 	"powershell": {
 		"issues": "Wygląda na to, że masz problemy z Windows PowerShell, proszę zapoznaj się z tym"

+ 2 - 1
webview-ui/src/i18n/locales/pt-BR/chat.json

@@ -267,7 +267,8 @@
 		"link": "Details",
 		"title": "Detalhes do erro",
 		"copyToClipboard": "Copiar para área de transferência",
-		"copied": "Copiado!"
+		"copied": "Copiado!",
+		"diagnostics": "Obter informações detalhadas do erro"
 	},
 	"powershell": {
 		"issues": "Parece que você está tendo problemas com o Windows PowerShell, por favor veja este"

+ 2 - 1
webview-ui/src/i18n/locales/ru/chat.json

@@ -268,7 +268,8 @@
 		"link": "Подробности",
 		"title": "Детали ошибки",
 		"copyToClipboard": "Скопировать в буфер обмена",
-		"copied": "Скопировано!"
+		"copied": "Скопировано!",
+		"diagnostics": "Получить подробную информацию об ошибке"
 	},
 	"powershell": {
 		"issues": "Похоже, у вас проблемы с Windows PowerShell, пожалуйста, ознакомьтесь с этим"

+ 2 - 1
webview-ui/src/i18n/locales/tr/chat.json

@@ -268,7 +268,8 @@
 		"link": "Details",
 		"title": "Hata Detayları",
 		"copyToClipboard": "Panoya Kopyala",
-		"copied": "Kopyalandı!"
+		"copied": "Kopyalandı!",
+		"diagnostics": "Ayrıntılı hata bilgisi al"
 	},
 	"powershell": {
 		"issues": "Windows PowerShell ile ilgili sorunlar yaşıyor gibi görünüyorsunuz, lütfen şu konuya bakın"

+ 2 - 1
webview-ui/src/i18n/locales/vi/chat.json

@@ -268,7 +268,8 @@
 		"link": "Chi tiết",
 		"title": "Chi tiết lỗi",
 		"copyToClipboard": "Sao chép vào clipboard",
-		"copied": "Đã sao chép!"
+		"copied": "Đã sao chép!",
+		"diagnostics": "Nhận thông tin lỗi chi tiết"
 	},
 	"powershell": {
 		"issues": "Có vẻ như bạn đang gặp vấn đề với Windows PowerShell, vui lòng xem"

+ 2 - 1
webview-ui/src/i18n/locales/zh-CN/chat.json

@@ -268,7 +268,8 @@
 		"link": "详情",
 		"title": "错误详情",
 		"copyToClipboard": "复制到剪贴板",
-		"copied": "已复制!"
+		"copied": "已复制!",
+		"diagnostics": "获取详细错误信息"
 	},
 	"powershell": {
 		"issues": "看起来您遇到了Windows PowerShell问题,请参阅此"

+ 2 - 1
webview-ui/src/i18n/locales/zh-TW/chat.json

@@ -301,7 +301,8 @@
 		"link": "詳情",
 		"title": "錯誤詳細資訊",
 		"copyToClipboard": "複製到剪貼簿",
-		"copied": "已複製!"
+		"copied": "已複製!",
+		"diagnostics": "取得詳細錯誤資訊"
 	},
 	"powershell": {
 		"issues": "您似乎遇到了 Windows PowerShell 的問題,請參閱此說明文件"