diff --git a/src/core/assistant-message/presentAssistantMessage.ts b/src/core/assistant-message/presentAssistantMessage.ts index ee3fa148b4..41543d4c29 100644 --- a/src/core/assistant-message/presentAssistantMessage.ts +++ b/src/core/assistant-message/presentAssistantMessage.ts @@ -2,6 +2,7 @@ import cloneDeep from "clone-deep" import { serializeError } from "serialize-error" import type { ToolName, ClineAsk, ToolProgressStatus } from "@roo-code/types" +import { TelemetryEventName } from "@roo-code/types" import { TelemetryService } from "@roo-code/telemetry" import { defaultModeSlug, getModeBySlug } from "../../shared/modes" @@ -307,6 +308,19 @@ export async function presentAssistantMessage(cline: Task) { const handleError = async (action: string, error: Error) => { const errorString = `Error ${action}: ${JSON.stringify(serializeError(error))}` + // Record the tool error for tracking and telemetry + cline.recordToolError(block.name as ToolName, error.message) + + // Capture detailed telemetry for tool failures using existing event + TelemetryService.instance.captureEvent(TelemetryEventName.DIFF_APPLICATION_ERROR, { + taskId: cline.taskId, + toolName: block.name, + action, + errorMessage: error.message, + errorType: error.constructor.name, + consecutiveMistakes: cline.consecutiveMistakeCount, + }) + await cline.say( "error", `Error ${action}:\n${error.message ?? JSON.stringify(serializeError(error), null, 2)}`, @@ -364,7 +378,22 @@ export async function presentAssistantMessage(cline: Task) { ) } catch (error) { cline.consecutiveMistakeCount++ - pushToolResult(formatResponse.toolError(error.message)) + // Record the validation error for tracking + cline.recordToolError(block.name as ToolName, `Validation failed: ${error.message}`) + + // Capture telemetry for validation failures using existing event + TelemetryService.instance.captureEvent(TelemetryEventName.SCHEMA_VALIDATION_ERROR, { + taskId: cline.taskId, + toolName: block.name, + errorMessage: error.message, + consecutiveMistakes: cline.consecutiveMistakeCount, + mode: mode ?? defaultModeSlug, + }) + + // Provide more helpful error message for tool validation failures + const enhancedErrorMessage = `Tool validation failed for ${block.name}: ${error.message}\n\nThis may indicate:\n- Invalid parameters provided to the tool\n- Tool not allowed in current mode\n- Missing required parameters\n\nPlease review the tool usage and try again with correct parameters.` + + pushToolResult(formatResponse.toolError(enhancedErrorMessage)) break } @@ -399,6 +428,9 @@ export async function presentAssistantMessage(cline: Task) { TelemetryService.instance.captureConsecutiveMistakeError(cline.taskId) } + // Capture telemetry for tool repetition using existing event + TelemetryService.instance.captureConsecutiveMistakeError(cline.taskId) + // Return tool result message about the repetition pushToolResult( formatResponse.toolError( diff --git a/src/core/prompts/responses.ts b/src/core/prompts/responses.ts index 3f38789fdc..14164f5b11 100644 --- a/src/core/prompts/responses.ts +++ b/src/core/prompts/responses.ts @@ -13,7 +13,31 @@ export const formatResponse = { toolApprovedWithFeedback: (feedback?: string) => `The user approved this operation and provided the following context:\n\n${feedback}\n`, - toolError: (error?: string) => `The tool execution failed with the following error:\n\n${error}\n`, + toolError: (error?: string) => { + const baseMessage = `The tool execution failed with the following error:\n\n${error}\n` + + // Add helpful suggestions based on common error patterns + let suggestions = "" + if (error) { + const lowerError = error.toLowerCase() + + if (lowerError.includes("permission") || lowerError.includes("access denied")) { + suggestions = + "\n\nSuggestions:\n• Check file permissions\n• Ensure the file is not locked by another process\n• Try using a different file path" + } else if (lowerError.includes("not found") || lowerError.includes("no such file")) { + suggestions = + "\n\nSuggestions:\n• Verify the file path is correct\n• Use list_files to check available files\n• Create the file first if it doesn't exist" + } else if (lowerError.includes("validation failed")) { + suggestions = + "\n\nSuggestions:\n• Check that all required parameters are provided\n• Verify parameter formats match expectations\n• Review the tool documentation for correct usage" + } else if (lowerError.includes("syntax error") || lowerError.includes("invalid")) { + suggestions = + "\n\nSuggestions:\n• Check the syntax of your input\n• Verify all brackets, quotes, and tags are properly closed\n• Try simplifying the operation" + } + } + + return baseMessage + suggestions + }, rooIgnoreError: (path: string) => `Access to ${path} is blocked by the .rooignore file settings. You must try to continue in the task without using this file, or ask the user to update the .rooignore file.`, @@ -30,8 +54,17 @@ If you require additional information from the user, use the ask_followup_questi Otherwise, if you have not completed the task and do not need additional information, then proceed with the next step of the task. (This is an automated message, so do not respond to it conversationally.)`, - tooManyMistakes: (feedback?: string) => - `You seem to be having trouble proceeding. The user has provided the following feedback to help guide you:\n\n${feedback}\n`, + tooManyMistakes: (feedback?: string) => { + let message = `You seem to be having trouble proceeding. This may indicate:\n• Tool parameters are incorrect\n• The approach needs to be changed\n• The task should be broken into smaller steps\n• A different tool might be more appropriate` + + if (feedback) { + message += `\n\nThe user has provided the following feedback to help guide you:\n\n${feedback}\n` + } else { + message += `\n\nConsider:\n• Re-reading files to understand current state\n• Using simpler, more targeted operations\n• Asking for clarification if the task is unclear` + } + + return message + }, missingToolParameterError: (paramName: string) => `Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${toolUseInstructionsReminder}`, diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 53b8ef5b87..07d8e2d021 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -1932,9 +1932,21 @@ export class Task extends EventEmitter { this.toolUsage[toolName].failures++ + // Record the failure in the repetition detector for better context + this.toolRepetitionDetector.recordToolFailure(toolName, error) + if (error) { this.emit("taskToolFailed", this.taskId, toolName, error) } + + // Add enhanced logging for debugging tool failures + console.debug(`[Task ${this.taskId}] Tool failure recorded:`, { + toolName, + error, + totalFailures: this.toolUsage[toolName].failures, + totalAttempts: this.toolUsage[toolName].attempts, + consecutiveMistakes: this.consecutiveMistakeCount, + }) } // Getters diff --git a/src/core/tools/ToolRepetitionDetector.ts b/src/core/tools/ToolRepetitionDetector.ts index 927b031e3b..f7f30ef5e1 100644 --- a/src/core/tools/ToolRepetitionDetector.ts +++ b/src/core/tools/ToolRepetitionDetector.ts @@ -9,6 +9,8 @@ export class ToolRepetitionDetector { private previousToolCallJson: string | null = null private consecutiveIdenticalToolCallCount: number = 0 private readonly consecutiveIdenticalToolCallLimit: number + private toolFailureHistory: Array<{ toolName: string; timestamp: number; reason?: string }> = [] + private readonly maxHistorySize = 10 /** * Creates a new ToolRepetitionDetector @@ -48,6 +50,10 @@ export class ToolRepetitionDetector { this.consecutiveIdenticalToolCallLimit > 0 && this.consecutiveIdenticalToolCallCount >= this.consecutiveIdenticalToolCallLimit ) { + // Generate enhanced error message with context + const recentFailures = this.getRecentFailureContext(currentToolCallBlock.name) + const contextMessage = this.generateContextualErrorMessage(currentToolCallBlock.name, recentFailures) + // Reset counters to allow recovery if user guides the AI past this point this.consecutiveIdenticalToolCallCount = 0 this.previousToolCallJson = null @@ -57,7 +63,7 @@ export class ToolRepetitionDetector { allowExecution: false, askUser: { messageKey: "mistake_limit_reached", - messageDetail: t("tools:toolRepetitionLimitReached", { toolName: currentToolCallBlock.name }), + messageDetail: contextMessage, }, } } @@ -66,6 +72,64 @@ export class ToolRepetitionDetector { return { allowExecution: true } } + /** + * Records a tool failure for better context in future error messages + */ + public recordToolFailure(toolName: string, reason?: string): void { + this.toolFailureHistory.push({ + toolName, + timestamp: Date.now(), + reason, + }) + + // Keep history size manageable + if (this.toolFailureHistory.length > this.maxHistorySize) { + this.toolFailureHistory.shift() + } + } + + /** + * Gets recent failure context for a specific tool + */ + private getRecentFailureContext(toolName: string): Array<{ reason?: string; timestamp: number }> { + const fiveMinutesAgo = Date.now() - 5 * 60 * 1000 + return this.toolFailureHistory + .filter((failure) => failure.toolName === toolName && failure.timestamp > fiveMinutesAgo) + .map((failure) => ({ reason: failure.reason, timestamp: failure.timestamp })) + } + + /** + * Generates a contextual error message based on recent failures + */ + private generateContextualErrorMessage( + toolName: string, + recentFailures: Array<{ reason?: string; timestamp: number }>, + ): string { + let message = t("tools:toolRepetitionLimitReached", { toolName }) + + if (recentFailures.length > 0) { + message += "\n\nRecent issues with this tool:" + const uniqueReasons = [...new Set(recentFailures.map((f) => f.reason).filter(Boolean))] + + if (uniqueReasons.length > 0) { + message += "\n" + uniqueReasons.map((reason) => `• ${reason}`).join("\n") + } + + message += "\n\nSuggestions:" + message += "\n• Try a different approach or tool" + message += "\n• Check if the parameters are correct" + message += "\n• Consider breaking down the task into smaller steps" + + if (toolName === "apply_diff" || toolName === "write_to_file") { + message += "\n• Use read_file first to understand the current file content" + } else if (toolName === "execute_command") { + message += "\n• Verify the command syntax and file paths" + } + } + + return message + } + /** * Serializes a ToolUse object into a canonical JSON string for comparison *