Skip to content

Commit f8b4bf7

Browse files
committed
feat: Add configurable error & repetition limit with unified control (#5654)
1 parent 916875d commit f8b4bf7

File tree

23 files changed

+101
-67
lines changed

23 files changed

+101
-67
lines changed

src/core/config/ProviderSettingsManager.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,9 +240,13 @@ export class ProviderSettingsManager {
240240

241241
private async migrateConsecutiveMistakeLimit(providerProfiles: ProviderProfiles) {
242242
try {
243-
for (const [_name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) {
243+
for (const [name, apiConfig] of Object.entries(providerProfiles.apiConfigs)) {
244244
if (apiConfig.consecutiveMistakeLimit == null) {
245245
apiConfig.consecutiveMistakeLimit = DEFAULT_CONSECUTIVE_MISTAKE_LIMIT
246+
} else {
247+
console.log(
248+
`[ProviderSettingsManager] Profile ${name} already has consecutiveMistakeLimit: ${apiConfig.consecutiveMistakeLimit}`,
249+
)
246250
}
247251
}
248252
} catch (error) {

src/core/task/Task.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,9 @@ export class Task extends EventEmitter<ClineEvents> {
290290
})
291291
}
292292

293-
this.toolRepetitionDetector = new ToolRepetitionDetector(3)
293+
this.toolRepetitionDetector = new ToolRepetitionDetector(
294+
this.consecutiveMistakeLimit === Infinity ? 0 : this.consecutiveMistakeLimit,
295+
)
294296

295297
onCreated?.(this)
296298

src/core/task/__tests__/Task.spec.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,12 +364,26 @@ describe("Cline", () => {
364364
startTask: false,
365365
})
366366

367-
// The toolRepetitionDetector should be initialized with MAX_SAFE_INTEGER when limit is Infinity
367+
// The toolRepetitionDetector should be initialized with 0 when limit is Infinity (unlimited)
368368
expect(cline.toolRepetitionDetector).toBeDefined()
369369
// We can't directly check the internal state, but we can verify the limit was converted
370370
expect(cline.consecutiveMistakeLimit).toBe(Infinity)
371371
})
372372

373+
it("should pass consecutiveMistakeLimit to ToolRepetitionDetector", () => {
374+
const cline = new Task({
375+
provider: mockProvider,
376+
apiConfiguration: mockApiConfig,
377+
consecutiveMistakeLimit: 5,
378+
task: "test task",
379+
startTask: false,
380+
})
381+
382+
// The toolRepetitionDetector should be initialized with the same limit
383+
expect(cline.toolRepetitionDetector).toBeDefined()
384+
expect(cline.consecutiveMistakeLimit).toBe(5)
385+
})
386+
373387
it("should require either task or historyItem", () => {
374388
expect(() => {
375389
new Task({ provider: mockProvider, apiConfiguration: mockApiConfig })

src/core/tools/ToolRepetitionDetector.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,11 @@ export class ToolRepetitionDetector {
4343
this.previousToolCallJson = currentToolCallJson
4444
}
4545

46-
// Check if limit is reached
47-
if (this.consecutiveIdenticalToolCallCount >= this.consecutiveIdenticalToolCallLimit) {
46+
// Check if limit is reached (0 means unlimited)
47+
if (
48+
this.consecutiveIdenticalToolCallLimit > 0 &&
49+
this.consecutiveIdenticalToolCallCount >= this.consecutiveIdenticalToolCallLimit
50+
) {
4851
// Reset counters to allow recovery if user guides the AI past this point
4952
this.consecutiveIdenticalToolCallCount = 0
5053
this.previousToolCallJson = null

src/core/tools/__tests__/ToolRepetitionDetector.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,5 +301,16 @@ describe("ToolRepetitionDetector", () => {
301301
expect(result3.allowExecution).toBe(false)
302302
expect(result3.askUser).toBeDefined()
303303
})
304+
305+
it("should never block when limit is 0 (unlimited)", () => {
306+
const detector = new ToolRepetitionDetector(0)
307+
308+
// Try many identical calls
309+
for (let i = 0; i < 10; i++) {
310+
const result = detector.check(createToolUse("tool", "tool-name"))
311+
expect(result.allowExecution).toBe(true)
312+
expect(result.askUser).toBeUndefined()
313+
}
314+
})
304315
})
305316
})

webview-ui/src/i18n/locales/ca/settings.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,10 +368,10 @@
368368
"description": "Temps mínim entre sol·licituds d'API."
369369
},
370370
"consecutiveMistakeLimit": {
371-
"label": "Límit d'errors consecutius",
372-
"description": "Nombre d'errors consecutius abans de mostrar el diàleg 'Continua igualment'. Actual: {{value}}",
373-
"unlimitedDescription": "Reintents il·limitats activats (continuació automàtica). El diàleg no apareixerà mai.",
374-
"warning": "⚠️ Establir-ho a 0 permet reintents il·limitats que poden consumir un ús significatiu de l'API"
371+
"label": "Límit d'errors i repeticions",
372+
"description": "Nombre d'errors consecutius o accions repetides abans de mostrar el diàleg 'En Roo està tenint problemes'",
373+
"unlimitedDescription": "Reintents il·limitats habilitats (procediment automàtic). El diàleg no apareixerà mai.",
374+
"warning": "⚠️ Establir a 0 permet reintents il·limitats que poden consumir un ús significatiu de l'API"
375375
},
376376
"reasoningEffort": {
377377
"label": "Esforç de raonament del model",

webview-ui/src/i18n/locales/de/settings.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,10 +368,10 @@
368368
"description": "Minimale Zeit zwischen API-Anfragen."
369369
},
370370
"consecutiveMistakeLimit": {
371-
"label": "Limit für aufeinanderfolgende Fehler",
372-
"description": "Anzahl der aufeinanderfolgenden Fehler, bevor der Dialog 'Trotzdem fortfahren' angezeigt wird. Aktuell: {{value}}",
373-
"unlimitedDescription": "Unbegrenzte Wiederholungen aktiviert (automatisches Fortfahren). Der Dialog wird nie angezeigt.",
374-
"warning": "⚠️ Das Setzen auf 0 erlaubt unbegrenzte Wiederholungen, was zu einem erheblichen API-Verbrauch führen kann"
371+
"label": "Fehler- & Wiederholungslimit",
372+
"description": "Anzahl aufeinanderfolgender Fehler oder wiederholter Aktionen, bevor der Dialog 'Roo hat Probleme' angezeigt wird",
373+
"unlimitedDescription": "Unbegrenzte Wiederholungen aktiviert (automatisches Fortfahren). Der Dialog wird niemals angezeigt.",
374+
"warning": "⚠️ Das Setzen auf 0 erlaubt unbegrenzte Wiederholungen, was zu erheblichem API-Verbrauch führen kann"
375375
},
376376
"reasoningEffort": {
377377
"label": "Modell-Denkaufwand",

webview-ui/src/i18n/locales/en/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -368,8 +368,8 @@
368368
"description": "Minimum time between API requests."
369369
},
370370
"consecutiveMistakeLimit": {
371-
"label": "Consecutive Mistake Limit",
372-
"description": "Number of consecutive mistakes before showing 'Proceed Anyways' dialog. Current: {{value}}",
371+
"label": "Error & Repetition Limit",
372+
"description": "Number of consecutive errors or repeated actions before showing 'Roo is having trouble' dialog",
373373
"unlimitedDescription": "Unlimited retries enabled (auto-proceed). The dialog will never appear.",
374374
"warning": "⚠️ Setting to 0 allows unlimited retries which may consume significant API usage"
375375
},

webview-ui/src/i18n/locales/es/settings.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -368,10 +368,10 @@
368368
"description": "Tiempo mínimo entre solicitudes de API."
369369
},
370370
"consecutiveMistakeLimit": {
371-
"label": "Límite de errores consecutivos",
372-
"description": "Número de errores consecutivos antes de mostrar el diálogo 'Proceder de todos modos'. Actual: {{value}}",
371+
"label": "Límite de errores y repeticiones",
372+
"description": "Número de errores consecutivos o acciones repetidas antes de mostrar el diálogo 'Roo está teniendo problemas'",
373373
"unlimitedDescription": "Reintentos ilimitados habilitados (proceder automáticamente). El diálogo nunca aparecerá.",
374-
"warning": "⚠️ Establecer el valor en 0 permite reintentos ilimitados que pueden consumir un uso significativo de la API"
374+
"warning": "⚠️ Establecer en 0 permite reintentos ilimitados que pueden consumir un uso significativo de la API"
375375
},
376376
"reasoningEffort": {
377377
"label": "Esfuerzo de razonamiento del modelo",

webview-ui/src/i18n/locales/fr/settings.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,10 +368,10 @@
368368
"description": "Temps minimum entre les requêtes API."
369369
},
370370
"consecutiveMistakeLimit": {
371-
"label": "Limite d'erreurs consécutives",
372-
"description": "Nombre d'erreurs consécutives avant d'afficher la boîte de dialogue 'Continuer quand même'. Actuel : {{value}}",
373-
"unlimitedDescription": "Nouvelles tentatives illimitées activées (poursuite automatique). La boîte de dialogue n'apparaîtra jamais.",
374-
"warning": "⚠️ Mettre à 0 autorise des nouvelles tentatives illimitées, ce qui peut consommer une utilisation importante de l'API"
371+
"label": "Limite d'erreurs et de répétitions",
372+
"description": "Nombre d'erreurs consécutives ou d'actions répétées avant d'afficher la boîte de dialogue 'Roo a des difficultés'",
373+
"unlimitedDescription": "Réessais illimités activés (poursuite automatique). La boîte de dialogue n'apparaîtra jamais.",
374+
"warning": "⚠️ Mettre à 0 autorise des réessais illimités, ce qui peut consommer une utilisation importante de l'API"
375375
},
376376
"reasoningEffort": {
377377
"label": "Effort de raisonnement du modèle",

0 commit comments

Comments
 (0)