From 4fc418c130142a88e5e2401555075d7c95c4d7ba Mon Sep 17 00:00:00 2001 From: Roo Code Date: Thu, 15 Jan 2026 11:07:18 +0000 Subject: [PATCH] feat(ROO-538): add send error to support button for authenticated users --- packages/types/src/vscode-extension-host.ts | 1 + src/core/webview/webviewMessageHandler.ts | 72 ++++++++++++++++++ webview-ui/src/components/chat/ErrorRow.tsx | 42 ++++++++++- .../chat/__tests__/ErrorRow.spec.tsx | 75 ++++++++++++++++++- webview-ui/src/i18n/locales/en/chat.json | 2 + 5 files changed, 186 insertions(+), 6 deletions(-) diff --git a/packages/types/src/vscode-extension-host.ts b/packages/types/src/vscode-extension-host.ts index bce6c993bc7..ef38a481bf1 100644 --- a/packages/types/src/vscode-extension-host.ts +++ b/packages/types/src/vscode-extension-host.ts @@ -507,6 +507,7 @@ export interface WebviewMessage { | "openDebugApiHistory" | "openDebugUiHistory" | "downloadErrorDiagnostics" + | "sendErrorToSupport" | "requestClaudeCodeRateLimits" | "refreshCustomTools" | "requestModes" diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index ddd97dffd50..37e008ec7c8 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -3310,6 +3310,78 @@ export const webviewMessageHandler = async ( break } + case "sendErrorToSupport": { + const currentTask = provider.getCurrentTask() + if (!currentTask) { + vscode.window.showErrorMessage("No active task to send error details") + break + } + + try { + // Get the cloud API URL from the message values + const cloudApiUrl = + message.values?.cloudApiUrl || process.env.ROO_CODE_CLOUD_URL || "https://app.roocode.com" + const contactEndpoint = `${cloudApiUrl}/api/contact/issue` + + // Prepare the error details JSON + const errorData = { + timestamp: message.values?.timestamp || new Date().toISOString(), + version: message.values?.version || "", + provider: message.values?.provider || "", + model: message.values?.model || "", + details: message.values?.details || "", + taskId: currentTask.taskId, + } + + // Create a FormData-like structure + const errorJson = JSON.stringify(errorData, null, 2) + const boundary = `----RooCodeBoundary${Date.now()}` + + const body = [ + `--${boundary}`, + 'Content-Disposition: form-data; name="file"; filename="error-details.json"', + "Content-Type: application/json", + "", + errorJson, + `--${boundary}--`, + ].join("\r\n") + + // Get the session token from CloudService + const sessionToken = CloudService.hasInstance() + ? CloudService.instance.authService?.getSessionToken() + : undefined + + if (!sessionToken) { + vscode.window.showErrorMessage("You must be logged in to send error details to support") + break + } + + // Make the authenticated POST request + const response = await fetch(contactEndpoint, { + method: "POST", + headers: { + Authorization: `Bearer ${sessionToken}`, + "Content-Type": `multipart/form-data; boundary=${boundary}`, + }, + body: body, + }) + + if (response.ok) { + vscode.window.showInformationMessage("Error details sent to Roo Code support successfully") + } else { + const errorText = await response.text() + provider.log(`Failed to send error details to support: ${response.status} ${errorText}`) + vscode.window.showErrorMessage("Failed to send error details to support. Please try again later.") + } + } catch (error) { + provider.log( + `Error sending error details to support: ${error instanceof Error ? error.message : String(error)}`, + ) + vscode.window.showErrorMessage("Failed to send error details to support. Please try again later.") + } + break + } + default: { // console.log(`Unhandled message type: ${message.type}`) // diff --git a/webview-ui/src/components/chat/ErrorRow.tsx b/webview-ui/src/components/chat/ErrorRow.tsx index 50e7c67b5bb..85571f83488 100644 --- a/webview-ui/src/components/chat/ErrorRow.tsx +++ b/webview-ui/src/components/chat/ErrorRow.tsx @@ -1,7 +1,7 @@ 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, Microscope, Info } from "lucide-react" +import { BookOpenText, MessageCircleWarning, Copy, Check, Microscope, Info, Send } from "lucide-react" import { useCopyToClipboard } from "@src/utils/clipboard" import { vscode } from "@src/utils/vscode" @@ -94,8 +94,9 @@ export const ErrorRow = memo( const [showCopySuccess, setShowCopySuccess] = useState(false) const [isDetailsDialogOpen, setIsDetailsDialogOpen] = useState(false) const [showDetailsCopySuccess, setShowDetailsCopySuccess] = useState(false) + const [showSendSuccess, setShowSendSuccess] = useState(false) const { copyWithFeedback } = useCopyToClipboard() - const { version, apiConfiguration } = useExtensionState() + const { version, apiConfiguration, cloudIsAuthenticated, cloudApiUrl } = useExtensionState() const { provider, id: modelId } = useSelectedModel(apiConfiguration) const usesProxy = PROVIDERS.find((p) => p.value === provider)?.proxy ?? false @@ -133,6 +134,28 @@ export const ErrorRow = memo( [version, provider, modelId, errorDetails], ) + const handleSendToSupport = useCallback( + (e: React.MouseEvent) => { + e.stopPropagation() + vscode.postMessage({ + type: "sendErrorToSupport", + values: { + timestamp: new Date().toISOString(), + version, + provider, + model: modelId, + details: errorDetails || "", + cloudApiUrl, + }, + }) + setShowSendSuccess(true) + setTimeout(() => { + setShowSendSuccess(false) + }, 2000) + }, + [version, provider, modelId, errorDetails, cloudApiUrl], + ) + // Default titles for different error types const getDefaultTitle = () => { if (title) return title @@ -307,6 +330,21 @@ export const ErrorRow = memo( )} + {cloudIsAuthenticated && ( + + )}