@@ -40,6 +40,7 @@ import { ToolParamName, ToolResponse, DiffStrategy } from "../shared/tools"
4040import { UrlContentFetcher } from "../services/browser/UrlContentFetcher"
4141import { BrowserSession } from "../services/browser/BrowserSession"
4242import { McpHub } from "../services/mcp/McpHub"
43+ import { ToolRepetitionDetector } from "./ToolRepetitionDetector"
4344import { McpServerManager } from "../services/mcp/McpServerManager"
4445import { telemetryService } from "../services/telemetry/TelemetryService"
4546import { CheckpointServiceOptions , RepoPerTaskCheckpointService } from "../services/checkpoints"
@@ -165,6 +166,9 @@ export class Cline extends EventEmitter<ClineEvents> {
165166 consecutiveMistakeLimit : number
166167 consecutiveMistakeCountForApplyDiff : Map < string , number > = new Map ( )
167168
169+ // For tracking identical consecutive tool calls
170+ private toolRepetitionDetector : ToolRepetitionDetector
171+
168172 // Not private since it needs to be accessible by tools.
169173 providerRef : WeakRef < ClineProvider >
170174 private readonly globalStoragePath : string
@@ -263,6 +267,7 @@ export class Cline extends EventEmitter<ClineEvents> {
263267 }
264268
265269 this . diffStrategy = new MultiSearchReplaceDiffStrategy ( this . fuzzyMatchThreshold )
270+ this . toolRepetitionDetector = new ToolRepetitionDetector ( this . consecutiveMistakeLimit )
266271
267272 onCreated ?.( this )
268273
@@ -1857,6 +1862,46 @@ export class Cline extends EventEmitter<ClineEvents> {
18571862 break
18581863 }
18591864
1865+ // Check for identical consecutive tool calls
1866+ if ( ! block . partial ) {
1867+ // Use the detector to check for repetition, passing the ToolUse block directly
1868+ const repetitionCheck = this . toolRepetitionDetector . check ( block )
1869+
1870+ // If execution is not allowed, notify user and break
1871+ if ( ! repetitionCheck . allowExecution && repetitionCheck . askUser ) {
1872+ // Handle repetition similar to mistake_limit_reached pattern
1873+ const { response, text, images } = await this . ask (
1874+ repetitionCheck . askUser . messageKey as ClineAsk ,
1875+ repetitionCheck . askUser . messageDetail . replace ( "{toolName}" , block . name ) ,
1876+ )
1877+
1878+ if ( response === "messageResponse" ) {
1879+ // Add user feedback to userContent
1880+ this . userMessageContent . push (
1881+ {
1882+ type : "text" as const ,
1883+ text : `Tool repetition limit reached. User feedback: ${ text } ` ,
1884+ } ,
1885+ ...formatResponse . imageBlocks ( images ) ,
1886+ )
1887+
1888+ // Add user feedback to chat
1889+ await this . say ( "user_feedback" , text , images )
1890+
1891+ // Track tool repetition in telemetry
1892+ telemetryService . captureConsecutiveMistakeError ( this . taskId ) // Using existing telemetry method
1893+ }
1894+
1895+ // Return tool result message about the repetition
1896+ pushToolResult (
1897+ formatResponse . toolError (
1898+ `Tool call repetition limit reached for ${ block . name } . Please try a different approach.` ,
1899+ ) ,
1900+ )
1901+ break
1902+ }
1903+ }
1904+
18601905 switch ( block . name ) {
18611906 case "write_to_file" :
18621907 await writeToFileTool ( this , block , askApproval , handleError , pushToolResult , removeClosingTag )
0 commit comments