@@ -32,30 +32,38 @@ describe("ToolRepetitionDetector", () => {
3232 const detector = new ToolRepetitionDetector ( )
3333 // We'll verify this through behavior in subsequent tests
3434
35- // First call (counter = 1 )
35+ // First call (counter = 0 )
3636 const result1 = detector . check ( createToolUse ( "test" , "test-tool" ) )
3737 expect ( result1 . allowExecution ) . toBe ( true )
3838
39- // Second identical call (counter = 2 )
39+ // Second identical call (counter = 1 )
4040 const result2 = detector . check ( createToolUse ( "test" , "test-tool" ) )
4141 expect ( result2 . allowExecution ) . toBe ( true )
4242
43- // Third identical call (counter = 3) reaches the default limit
43+ // Third identical call (counter = 2)
4444 const result3 = detector . check ( createToolUse ( "test" , "test-tool" ) )
45- expect ( result3 . allowExecution ) . toBe ( false )
45+ expect ( result3 . allowExecution ) . toBe ( true )
46+
47+ // Fourth identical call (counter = 3) reaches the default limit
48+ const result4 = detector . check ( createToolUse ( "test" , "test-tool" ) )
49+ expect ( result4 . allowExecution ) . toBe ( false )
4650 } )
4751
4852 it ( "should use the custom limit when provided" , ( ) => {
4953 const customLimit = 2
5054 const detector = new ToolRepetitionDetector ( customLimit )
5155
52- // First call (counter = 1 )
56+ // First call (counter = 0 )
5357 const result1 = detector . check ( createToolUse ( "test" , "test-tool" ) )
5458 expect ( result1 . allowExecution ) . toBe ( true )
5559
56- // Second identical call (counter = 2) reaches the custom limit
60+ // Second identical call (counter = 1)
5761 const result2 = detector . check ( createToolUse ( "test" , "test-tool" ) )
58- expect ( result2 . allowExecution ) . toBe ( false )
62+ expect ( result2 . allowExecution ) . toBe ( true )
63+
64+ // Third identical call (counter = 2) reaches the custom limit
65+ const result3 = detector . check ( createToolUse ( "test" , "test-tool" ) )
66+ expect ( result3 . allowExecution ) . toBe ( false )
5967 } )
6068 } )
6169
@@ -97,17 +105,21 @@ describe("ToolRepetitionDetector", () => {
97105 it ( "should allow execution when repetition is below limit and block when limit reached" , ( ) => {
98106 const detector = new ToolRepetitionDetector ( 3 )
99107
100- // First call (counter = 1 )
108+ // First call (counter = 0 )
101109 const result1 = detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
102110 expect ( result1 . allowExecution ) . toBe ( true )
103111
104- // Second identical call (counter = 2 )
112+ // Second identical call (counter = 1 )
105113 const result2 = detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
106114 expect ( result2 . allowExecution ) . toBe ( true )
107115
108- // Third identical call (counter = 3) reaches limit
116+ // Third identical call (counter = 2)
109117 const result3 = detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
110- expect ( result3 . allowExecution ) . toBe ( false )
118+ expect ( result3 . allowExecution ) . toBe ( true )
119+
120+ // Fourth identical call (counter = 3) reaches limit
121+ const result4 = detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
122+ expect ( result4 . allowExecution ) . toBe ( false )
111123 } )
112124 } )
113125
@@ -116,13 +128,16 @@ describe("ToolRepetitionDetector", () => {
116128 it ( "should block execution when repetition reaches the limit" , ( ) => {
117129 const detector = new ToolRepetitionDetector ( 3 )
118130
119- // First call (counter = 1)
131+ // First call (counter = 0)
132+ detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
133+
134+ // Second identical call (counter = 1)
120135 detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
121136
122- // Second identical call (counter = 2)
137+ // Third identical call (counter = 2)
123138 detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
124139
125- // Third identical call (counter = 3) - should reach limit
140+ // Fourth identical call (counter = 3) - should reach limit
126141 const result = detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
127142
128143 expect ( result . allowExecution ) . toBe ( false )
@@ -136,6 +151,7 @@ describe("ToolRepetitionDetector", () => {
136151
137152 // Reach the limit
138153 detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
154+ detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
139155 const limitResult = detector . check ( createToolUse ( "repeat" , "repeat-tool" ) ) // This reaches limit
140156 expect ( limitResult . allowExecution ) . toBe ( false )
141157
@@ -152,6 +168,7 @@ describe("ToolRepetitionDetector", () => {
152168
153169 // Reach the limit with a specific tool
154170 detector . check ( createToolUse ( "problem" , "problem-tool" ) )
171+ detector . check ( createToolUse ( "problem" , "problem-tool" ) )
155172 const limitResult = detector . check ( createToolUse ( "problem" , "problem-tool" ) ) // This reaches limit
156173 expect ( limitResult . allowExecution ) . toBe ( false )
157174
@@ -165,13 +182,17 @@ describe("ToolRepetitionDetector", () => {
165182
166183 // Reach the limit
167184 detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
185+ detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
168186 const limitResult = detector . check ( createToolUse ( "repeat" , "repeat-tool" ) ) // This reaches limit
169187 expect ( limitResult . allowExecution ) . toBe ( false )
170188
171189 // First call after reset
172190 detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
173191
174- // Second identical call (counter = 2) should reach limit again
192+ // Second call after reset
193+ detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
194+
195+ // Third identical call (counter = 2) should reach limit again
175196 const result = detector . check ( createToolUse ( "repeat" , "repeat-tool" ) )
176197 expect ( result . allowExecution ) . toBe ( false )
177198 expect ( result . askUser ) . toBeDefined ( )
@@ -186,6 +207,7 @@ describe("ToolRepetitionDetector", () => {
186207
187208 // Reach the limit
188209 detector . check ( createToolUse ( "test" , toolName ) )
210+ detector . check ( createToolUse ( "test" , toolName ) )
189211 const result = detector . check ( createToolUse ( "test" , toolName ) )
190212
191213 expect ( result . allowExecution ) . toBe ( false )
@@ -201,7 +223,8 @@ describe("ToolRepetitionDetector", () => {
201223 // Create an empty tool call - a tool with no parameters
202224 // Use the empty tool directly in the check calls
203225 detector . check ( createToolUse ( "empty-tool" , "empty-tool" ) )
204- const result = detector . check ( createToolUse ( "empty-tool" ) )
226+ detector . check ( createToolUse ( "empty-tool" , "empty-tool" ) )
227+ const result = detector . check ( createToolUse ( "empty-tool" , "empty-tool" ) )
205228
206229 expect ( result . allowExecution ) . toBe ( false )
207230 expect ( result . askUser ) . toBeDefined ( )
@@ -210,7 +233,7 @@ describe("ToolRepetitionDetector", () => {
210233 it ( "should handle different tool names with identical serialized JSON" , ( ) => {
211234 const detector = new ToolRepetitionDetector ( 2 )
212235
213- // First, call with tool-name-1 twice to set up the counter
236+ // First, call with tool-name-1 to set up the counter
214237 const toolUse1 = createToolUse ( "tool-name-1" , "tool-name-1" , { param : "value" } )
215238 detector . check ( toolUse1 )
216239
@@ -223,21 +246,25 @@ describe("ToolRepetitionDetector", () => {
223246 ; ( detector as any ) . serializeToolUse = ( tool : ToolUse ) => {
224247 // Use string comparison for the name since it's technically an enum
225248 if ( String ( tool . name ) === "tool-name-2" ) {
226- return ( detector as any ) . serializeToolUse ( toolUse1 ) // Return the same JSON as toolUse1
249+ return originalSerialize . call ( detector , toolUse1 ) // Return the same JSON as toolUse1
227250 }
228- return originalSerialize ( tool )
251+ return originalSerialize . call ( detector , tool )
229252 }
230253
231- // This should detect as a repetition now
232- const result = detector . check ( toolUse2 )
254+ // Second call - this should be considered identical due to our mock
255+ const result2 = detector . check ( toolUse2 )
256+ expect ( result2 . allowExecution ) . toBe ( true ) // Still allowed (counter = 1)
257+
258+ // Third call - should be blocked (limit is 2)
259+ const result3 = detector . check ( toolUse2 )
233260
234261 // Restore the original method
235262 ; ( detector as any ) . serializeToolUse = originalSerialize
236263
237264 // Since we're directly manipulating the internal state for testing,
238- // we still expect it to consider this a repetition
239- expect ( result . allowExecution ) . toBe ( false )
240- expect ( result . askUser ) . toBeDefined ( )
265+ // we expect it to consider this a repetition
266+ expect ( result3 . allowExecution ) . toBe ( false )
267+ expect ( result3 . askUser ) . toBeDefined ( )
241268 } )
242269
243270 it ( "should treat tools with same parameters in different order as identical" , ( ) => {
@@ -247,11 +274,13 @@ describe("ToolRepetitionDetector", () => {
247274 const toolUse1 = createToolUse ( "same-tool" , "same-tool" , { a : "1" , b : "2" , c : "3" } )
248275 detector . check ( toolUse1 )
249276
250- // Create tool with same parameters but in different order
277+ // Second call with same parameters but in different order
251278 const toolUse2 = createToolUse ( "same-tool" , "same-tool" , { c : "3" , a : "1" , b : "2" } )
279+ detector . check ( toolUse2 )
252280
253- // This should still detect as a repetition due to canonical JSON with sorted keys
254- const result = detector . check ( toolUse2 )
281+ // Third call - should be blocked (limit is 2)
282+ const toolUse3 = createToolUse ( "same-tool" , "same-tool" , { b : "2" , c : "3" , a : "1" } )
283+ const result = detector . check ( toolUse3 )
255284
256285 // Since parameters are sorted alphabetically in the serialized JSON,
257286 // these should be considered identical
@@ -262,44 +291,56 @@ describe("ToolRepetitionDetector", () => {
262291
263292 // ===== Explicit Nth Call Blocking tests =====
264293 describe ( "explicit Nth call blocking behavior" , ( ) => {
265- it ( "should block on the 1st call for limit 1" , ( ) => {
294+ it ( "should allow the 1st call but block on the 2nd call for limit 1" , ( ) => {
266295 const detector = new ToolRepetitionDetector ( 1 )
267296
268- // First call (counter = 1) should be blocked
269- const result = detector . check ( createToolUse ( "tool" , "tool-name" ) )
297+ // First call (counter = 0) should be allowed
298+ const result1 = detector . check ( createToolUse ( "tool" , "tool-name" ) )
299+ expect ( result1 . allowExecution ) . toBe ( true )
300+ expect ( result1 . askUser ) . toBeUndefined ( )
270301
271- expect ( result . allowExecution ) . toBe ( false )
272- expect ( result . askUser ) . toBeDefined ( )
302+ // Second identical call (counter = 1) should be blocked
303+ const result2 = detector . check ( createToolUse ( "tool" , "tool-name" ) )
304+ expect ( result2 . allowExecution ) . toBe ( false )
305+ expect ( result2 . askUser ) . toBeDefined ( )
273306 } )
274307
275- it ( "should block on the 2nd call for limit 2" , ( ) => {
308+ it ( "should allow first 2 calls but block on the 3rd call for limit 2" , ( ) => {
276309 const detector = new ToolRepetitionDetector ( 2 )
277310
278- // First call (counter = 1 )
311+ // First call (counter = 0 )
279312 const result1 = detector . check ( createToolUse ( "tool" , "tool-name" ) )
280313 expect ( result1 . allowExecution ) . toBe ( true )
281314
282- // Second call (counter = 2) should be blocked
315+ // Second identical call (counter = 1)
283316 const result2 = detector . check ( createToolUse ( "tool" , "tool-name" ) )
284- expect ( result2 . allowExecution ) . toBe ( false )
285- expect ( result2 . askUser ) . toBeDefined ( )
317+ expect ( result2 . allowExecution ) . toBe ( true )
318+
319+ // Third identical call (counter = 2) should be blocked
320+ const result3 = detector . check ( createToolUse ( "tool" , "tool-name" ) )
321+ expect ( result3 . allowExecution ) . toBe ( false )
322+ expect ( result3 . askUser ) . toBeDefined ( )
286323 } )
287324
288- it ( "should block on the 3rd call for limit 3 (default)" , ( ) => {
325+ it ( "should allow first 3 calls but block on the 4th call for limit 3 (default)" , ( ) => {
289326 const detector = new ToolRepetitionDetector ( 3 )
290327
291- // First call (counter = 1 )
328+ // First call (counter = 0 )
292329 const result1 = detector . check ( createToolUse ( "tool" , "tool-name" ) )
293330 expect ( result1 . allowExecution ) . toBe ( true )
294331
295- // Second call (counter = 2 )
332+ // Second identical call (counter = 1 )
296333 const result2 = detector . check ( createToolUse ( "tool" , "tool-name" ) )
297334 expect ( result2 . allowExecution ) . toBe ( true )
298335
299- // Third call (counter = 3) should be blocked
336+ // Third identical call (counter = 2)
300337 const result3 = detector . check ( createToolUse ( "tool" , "tool-name" ) )
301- expect ( result3 . allowExecution ) . toBe ( false )
302- expect ( result3 . askUser ) . toBeDefined ( )
338+ expect ( result3 . allowExecution ) . toBe ( true )
339+
340+ // Fourth identical call (counter = 3) should be blocked
341+ const result4 = detector . check ( createToolUse ( "tool" , "tool-name" ) )
342+ expect ( result4 . allowExecution ) . toBe ( false )
343+ expect ( result4 . askUser ) . toBeDefined ( )
303344 } )
304345
305346 it ( "should never block when limit is 0 (unlimited)" , ( ) => {
@@ -318,18 +359,18 @@ describe("ToolRepetitionDetector", () => {
318359 const detector5 = new ToolRepetitionDetector ( 5 )
319360 const tool = createToolUse ( "tool" , "tool-name" )
320361
321- // First 4 calls should be allowed
322- for ( let i = 0 ; i < 4 ; i ++ ) {
362+ // First 5 calls should be allowed
363+ for ( let i = 0 ; i < 5 ; i ++ ) {
323364 const result = detector5 . check ( tool )
324365 expect ( result . allowExecution ) . toBe ( true )
325366 expect ( result . askUser ) . toBeUndefined ( )
326367 }
327368
328- // 5th call should be blocked
329- const result5 = detector5 . check ( tool )
330- expect ( result5 . allowExecution ) . toBe ( false )
331- expect ( result5 . askUser ) . toBeDefined ( )
332- expect ( result5 . askUser ?. messageKey ) . toBe ( "mistake_limit_reached" )
369+ // 6th call should be blocked
370+ const result6 = detector5 . check ( tool )
371+ expect ( result6 . allowExecution ) . toBe ( false )
372+ expect ( result6 . askUser ) . toBeDefined ( )
373+ expect ( result6 . askUser ?. messageKey ) . toBe ( "mistake_limit_reached" )
333374 } )
334375
335376 it ( "should reset counter after blocking and allow new attempts" , ( ) => {
@@ -339,7 +380,10 @@ describe("ToolRepetitionDetector", () => {
339380 // First call allowed
340381 expect ( detector . check ( tool ) . allowExecution ) . toBe ( true )
341382
342- // Second call should block (limit is 2)
383+ // Second call allowed
384+ expect ( detector . check ( tool ) . allowExecution ) . toBe ( true )
385+
386+ // Third call should block (limit is 2)
343387 const blocked = detector . check ( tool )
344388 expect ( blocked . allowExecution ) . toBe ( false )
345389
0 commit comments