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.
This commit is contained in:
Roo Code 2025-09-08 20:50:35 +00:00
parent 47c21fb10b
commit eef6c0313c
7 changed files with 197 additions and 17 deletions

View file

@ -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"))
})
})

View file

@ -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()

View file

@ -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
}

View file

@ -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 [

View file

@ -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
}

View file

@ -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
}

View file

@ -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))