From eef6c0313cf200f9866fd92e963c06ae1485104a Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 8 Sep 2025 20:50:35 +0000 Subject: [PATCH] feat: add custom error titles to all tool error reporting - Updated generateImageTool.ts with 7 custom error titles - Updated executeCommandTool.ts with Command Timeout error title - Updated askFollowupQuestionTool.ts with XML Parse Error title - Updated useMcpToolTool.ts with 5 MCP-related error titles - Updated searchAndReplaceTool.ts with File Not Found and File Read Error titles - Fixed all related test files to match new error format with metadata parameter This ensures all error reporting across tools uses the new custom title feature for better error display in the UI. --- .../tools/__tests__/generateImageTool.test.ts | 24 +++++++- .../tools/__tests__/useMcpToolTool.spec.ts | 60 +++++++++++++++++-- src/core/tools/askFollowupQuestionTool.ts | 12 +++- src/core/tools/executeCommandTool.ts | 12 +++- src/core/tools/generateImageTool.ts | 53 ++++++++++++++-- src/core/tools/searchAndReplaceTool.ts | 8 ++- src/core/tools/useMcpToolTool.ts | 45 +++++++++++++- 7 files changed, 197 insertions(+), 17 deletions(-) diff --git a/src/core/tools/__tests__/generateImageTool.test.ts b/src/core/tools/__tests__/generateImageTool.test.ts index 0a12bebbe2..9084dbdbed 100644 --- a/src/core/tools/__tests__/generateImageTool.test.ts +++ b/src/core/tools/__tests__/generateImageTool.test.ts @@ -277,7 +277,17 @@ describe("generateImageTool", () => { mockRemoveClosingTag, ) - expect(mockCline.say).toHaveBeenCalledWith("error", expect.stringContaining("Input image not found")) + expect(mockCline.say).toHaveBeenCalledWith( + "error", + expect.stringContaining("Input image not found"), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "Input Image Not Found" }, + }, + ) expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Input image not found")) }) @@ -302,7 +312,17 @@ describe("generateImageTool", () => { mockRemoveClosingTag, ) - expect(mockCline.say).toHaveBeenCalledWith("error", expect.stringContaining("Unsupported image format")) + expect(mockCline.say).toHaveBeenCalledWith( + "error", + expect.stringContaining("Unsupported image format"), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "Unsupported Image Format" }, + }, + ) expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("Unsupported image format")) }) }) diff --git a/src/core/tools/__tests__/useMcpToolTool.spec.ts b/src/core/tools/__tests__/useMcpToolTool.spec.ts index 8738e059e5..e964c921ae 100644 --- a/src/core/tools/__tests__/useMcpToolTool.spec.ts +++ b/src/core/tools/__tests__/useMcpToolTool.spec.ts @@ -151,7 +151,17 @@ describe("useMcpToolTool", () => { expect(mockTask.consecutiveMistakeCount).toBe(1) expect(mockTask.recordToolError).toHaveBeenCalledWith("use_mcp_tool") - expect(mockTask.say).toHaveBeenCalledWith("error", expect.stringContaining("invalid JSON argument")) + expect(mockTask.say).toHaveBeenCalledWith( + "error", + expect.stringContaining("invalid JSON argument"), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "Invalid JSON Arguments" }, + }, + ) expect(mockPushToolResult).toHaveBeenCalledWith("Tool error: Invalid args for test_server:test_tool") }) }) @@ -343,7 +353,17 @@ describe("useMcpToolTool", () => { expect(mockTask.consecutiveMistakeCount).toBe(1) expect(mockTask.recordToolError).toHaveBeenCalledWith("use_mcp_tool") - expect(mockTask.say).toHaveBeenCalledWith("error", expect.stringContaining("does not exist")) + expect(mockTask.say).toHaveBeenCalledWith( + "error", + expect.stringContaining("does not exist"), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "MCP Tool Not Found" }, + }, + ) // Check that the error message contains available tools expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("existing-tool-1")) expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("existing-tool-2")) @@ -390,7 +410,17 @@ describe("useMcpToolTool", () => { expect(mockTask.consecutiveMistakeCount).toBe(1) expect(mockTask.recordToolError).toHaveBeenCalledWith("use_mcp_tool") - expect(mockTask.say).toHaveBeenCalledWith("error", expect.stringContaining("does not exist")) + expect(mockTask.say).toHaveBeenCalledWith( + "error", + expect.stringContaining("does not exist"), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "MCP Tool Not Found" }, + }, + ) expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("No tools available")) }) @@ -484,7 +514,17 @@ describe("useMcpToolTool", () => { // Assert expect(mockTask.consecutiveMistakeCount).toBe(1) expect(mockTask.recordToolError).toHaveBeenCalledWith("use_mcp_tool") - expect(mockTask.say).toHaveBeenCalledWith("error", expect.stringContaining("not configured")) + expect(mockTask.say).toHaveBeenCalledWith( + "error", + expect.stringContaining("not configured"), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "MCP Server Not Found" }, + }, + ) expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("s1")) expect(callToolMock).not.toHaveBeenCalled() expect(mockAskApproval).not.toHaveBeenCalled() @@ -527,7 +567,17 @@ describe("useMcpToolTool", () => { // Assert expect(mockTask.consecutiveMistakeCount).toBe(1) expect(mockTask.recordToolError).toHaveBeenCalledWith("use_mcp_tool") - expect(mockTask.say).toHaveBeenCalledWith("error", expect.stringContaining("not configured")) + expect(mockTask.say).toHaveBeenCalledWith( + "error", + expect.stringContaining("not configured"), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "MCP Server Not Found" }, + }, + ) expect(mockPushToolResult).toHaveBeenCalledWith(expect.stringContaining("No servers available")) expect(callToolMock).not.toHaveBeenCalled() expect(mockAskApproval).not.toHaveBeenCalled() diff --git a/src/core/tools/askFollowupQuestionTool.ts b/src/core/tools/askFollowupQuestionTool.ts index e736936887..4a96d55d93 100644 --- a/src/core/tools/askFollowupQuestionTool.ts +++ b/src/core/tools/askFollowupQuestionTool.ts @@ -48,7 +48,17 @@ export async function askFollowupQuestionTool( } catch (error) { cline.consecutiveMistakeCount++ cline.recordToolError("ask_followup_question") - await cline.say("error", `Failed to parse operations: ${error.message}`) + await cline.say( + "error", + `Failed to parse operations: ${error.message}`, + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "XML Parse Error" }, + }, + ) pushToolResult(formatResponse.toolError("Invalid operations xml format")) return } diff --git a/src/core/tools/executeCommandTool.ts b/src/core/tools/executeCommandTool.ts index 2c7ce0d023..f8b8241a53 100644 --- a/src/core/tools/executeCommandTool.ts +++ b/src/core/tools/executeCommandTool.ts @@ -271,7 +271,17 @@ export async function executeCommand( if (isTimedOut) { const status: CommandExecutionStatus = { executionId, status: "timeout" } provider?.postMessageToWebview({ type: "commandExecutionStatus", text: JSON.stringify(status) }) - await task.say("error", t("common:errors:command_timeout", { seconds: commandExecutionTimeoutSeconds })) + await task.say( + "error", + t("common:errors:command_timeout", { seconds: commandExecutionTimeoutSeconds }), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "Command Timeout" }, + }, + ) task.terminalProcess = undefined return [ diff --git a/src/core/tools/generateImageTool.ts b/src/core/tools/generateImageTool.ts index 749e7cff9a..4ca9888ce7 100644 --- a/src/core/tools/generateImageTool.ts +++ b/src/core/tools/generateImageTool.ts @@ -73,7 +73,17 @@ export async function generateImageTool( // Check if input image exists const inputImageExists = await fileExistsAtPath(inputImageFullPath) if (!inputImageExists) { - await cline.say("error", `Input image not found: ${getReadablePath(cline.cwd, inputImagePath)}`) + await cline.say( + "error", + `Input image not found: ${getReadablePath(cline.cwd, inputImagePath)}`, + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "Input Image Not Found" }, + }, + ) pushToolResult( formatResponse.toolError(`Input image not found: ${getReadablePath(cline.cwd, inputImagePath)}`), ) @@ -99,6 +109,13 @@ export async function generateImageTool( await cline.say( "error", `Unsupported image format: ${imageExtension}. Supported formats: ${supportedFormats.join(", ")}`, + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "Unsupported Image Format" }, + }, ) pushToolResult( formatResponse.toolError( @@ -115,6 +132,13 @@ export async function generateImageTool( await cline.say( "error", `Failed to read input image: ${error instanceof Error ? error.message : "Unknown error"}`, + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "Failed to Read Input Image" }, + }, ) pushToolResult( formatResponse.toolError( @@ -135,6 +159,13 @@ export async function generateImageTool( await cline.say( "error", "OpenRouter API key is required for image generation. Please configure it in the Image Generation experimental settings.", + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "Missing API Key" }, + }, ) pushToolResult( formatResponse.toolError( @@ -188,14 +219,26 @@ export async function generateImageTool( ) if (!result.success) { - await cline.say("error", result.error || "Failed to generate image") + await cline.say( + "error", + result.error || "Failed to generate image", + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "Image Generation Failed" }, + }, + ) pushToolResult(formatResponse.toolError(result.error || "Failed to generate image")) return } if (!result.imageData) { const errorMessage = "No image data received" - await cline.say("error", errorMessage) + await cline.say("error", errorMessage, undefined, undefined, undefined, undefined, { + metadata: { title: "No Image Data" }, + }) pushToolResult(formatResponse.toolError(errorMessage)) return } @@ -204,7 +247,9 @@ export async function generateImageTool( const base64Match = result.imageData.match(/^data:image\/(png|jpeg|jpg);base64,(.+)$/) if (!base64Match) { const errorMessage = "Invalid image format received" - await cline.say("error", errorMessage) + await cline.say("error", errorMessage, undefined, undefined, undefined, undefined, { + metadata: { title: "Invalid Image Format" }, + }) pushToolResult(formatResponse.toolError(errorMessage)) return } diff --git a/src/core/tools/searchAndReplaceTool.ts b/src/core/tools/searchAndReplaceTool.ts index 4912934415..1d017c8c1e 100644 --- a/src/core/tools/searchAndReplaceTool.ts +++ b/src/core/tools/searchAndReplaceTool.ts @@ -137,7 +137,9 @@ export async function searchAndReplaceTool( const formattedError = formatResponse.toolError( `File does not exist at path: ${absolutePath}\nThe specified file could not be found. Please verify the file path and try again.`, ) - await cline.say("error", formattedError) + await cline.say("error", formattedError, undefined, undefined, undefined, undefined, { + metadata: { title: "File Not Found" }, + }) pushToolResult(formattedError) return } @@ -156,7 +158,9 @@ export async function searchAndReplaceTool( error instanceof Error ? error.message : String(error) }\nPlease verify file permissions and try again.` const formattedError = formatResponse.toolError(errorMessage) - await cline.say("error", formattedError) + await cline.say("error", formattedError, undefined, undefined, undefined, undefined, { + metadata: { title: "File Read Error" }, + }) pushToolResult(formattedError) return } diff --git a/src/core/tools/useMcpToolTool.ts b/src/core/tools/useMcpToolTool.ts index 41697ab979..8801a487d6 100644 --- a/src/core/tools/useMcpToolTool.ts +++ b/src/core/tools/useMcpToolTool.ts @@ -62,7 +62,17 @@ async function validateParams( } catch (error) { cline.consecutiveMistakeCount++ cline.recordToolError("use_mcp_tool") - await cline.say("error", t("mcp:errors.invalidJsonArgument", { toolName: params.tool_name })) + await cline.say( + "error", + t("mcp:errors.invalidJsonArgument", { toolName: params.tool_name }), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "Invalid JSON Arguments" }, + }, + ) pushToolResult( formatResponse.toolError( @@ -109,7 +119,17 @@ async function validateToolExists( cline.consecutiveMistakeCount++ cline.recordToolError("use_mcp_tool") - await cline.say("error", t("mcp:errors.serverNotFound", { serverName, availableServers })) + await cline.say( + "error", + t("mcp:errors.serverNotFound", { serverName, availableServers }), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "MCP Server Not Found" }, + }, + ) pushToolResult(formatResponse.unknownMcpServerError(serverName, availableServersArray)) return { isValid: false, availableTools: [] } @@ -127,6 +147,13 @@ async function validateToolExists( serverName, availableTools: "No tools available", }), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "MCP Tool Not Found" }, + }, ) pushToolResult(formatResponse.unknownMcpToolError(serverName, toolName, [])) @@ -149,6 +176,13 @@ async function validateToolExists( serverName, availableTools: availableToolNames.join(", "), }), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "MCP Tool Not Found" }, + }, ) pushToolResult(formatResponse.unknownMcpToolError(serverName, toolName, availableToolNames)) @@ -171,6 +205,13 @@ async function validateToolExists( availableTools: enabledToolNames.length > 0 ? enabledToolNames.join(", ") : "No enabled tools available", }), + undefined, + undefined, + undefined, + undefined, + { + metadata: { title: "MCP Tool Disabled" }, + }, ) pushToolResult(formatResponse.unknownMcpToolError(serverName, toolName, enabledToolNames))