@@ -323,6 +323,7 @@ describe('Gemini Client (client.ts)', () => {
323323 getWorkingDir : vi . fn ( ) . mockReturnValue ( '/test/dir' ) ,
324324 getFileService : vi . fn ( ) . mockReturnValue ( fileService ) ,
325325 getMaxSessionTurns : vi . fn ( ) . mockReturnValue ( 0 ) ,
326+ getThinkingIdleThresholdMs : vi . fn ( ) . mockReturnValue ( 5 * 60 * 1000 ) ,
326327 getSessionTokenLimit : vi . fn ( ) . mockReturnValue ( 32000 ) ,
327328 getNoBrowser : vi . fn ( ) . mockReturnValue ( false ) ,
328329 getUsageStatisticsEnabled : vi . fn ( ) . mockReturnValue ( true ) ,
@@ -427,6 +428,119 @@ describe('Gemini Client (client.ts)', () => {
427428 } ) ;
428429 } ) ;
429430
431+ describe ( 'thinking block idle cleanup and latch' , ( ) => {
432+ let mockChat : Partial < GeminiChat > ;
433+
434+ beforeEach ( ( ) => {
435+ const mockStream = ( async function * ( ) {
436+ yield {
437+ type : GeminiEventType . Content ,
438+ value : 'response' ,
439+ } ;
440+ } ) ( ) ;
441+ mockTurnRunFn . mockReturnValue ( mockStream ) ;
442+
443+ mockChat = {
444+ addHistory : vi . fn ( ) ,
445+ getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
446+ stripThoughtsFromHistory : vi . fn ( ) ,
447+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
448+ } ;
449+ client [ 'chat' ] = mockChat as GeminiChat ;
450+ } ) ;
451+
452+ it ( 'should not strip thoughts on active session (< 5min idle)' , async ( ) => {
453+ // Simulate a recent API completion (2 minutes ago — within default 5 min threshold)
454+ client [ 'lastApiCompletionTimestamp' ] = Date . now ( ) - 2 * 60 * 1000 ;
455+ client [ 'thinkingClearLatched' ] = false ;
456+
457+ const gen = client . sendMessageStream (
458+ [ { text : 'Hello' } ] ,
459+ new AbortController ( ) . signal ,
460+ 'prompt-1' ,
461+ { type : SendMessageType . UserQuery } ,
462+ ) ;
463+ for await ( const _ of gen ) {
464+ /* drain */
465+ }
466+
467+ expect (
468+ mockChat . stripThoughtsFromHistoryKeepRecent ,
469+ ) . not . toHaveBeenCalled ( ) ;
470+ } ) ;
471+
472+ it ( 'should latch and strip thoughts after > 5min idle' , async ( ) => {
473+ // Simulate an old API completion (10 minutes ago — exceeds default 5 min threshold)
474+ client [ 'lastApiCompletionTimestamp' ] = Date . now ( ) - 10 * 60 * 1000 ;
475+ client [ 'thinkingClearLatched' ] = false ;
476+
477+ const gen = client . sendMessageStream (
478+ [ { text : 'Hello' } ] ,
479+ new AbortController ( ) . signal ,
480+ 'prompt-2' ,
481+ { type : SendMessageType . UserQuery } ,
482+ ) ;
483+ for await ( const _ of gen ) {
484+ /* drain */
485+ }
486+
487+ expect ( client [ 'thinkingClearLatched' ] ) . toBe ( true ) ;
488+ expect ( mockChat . stripThoughtsFromHistoryKeepRecent ) . toHaveBeenCalledWith (
489+ 1 ,
490+ ) ;
491+ } ) ;
492+
493+ it ( 'should keep stripping once latched even if idle < 5min' , async ( ) => {
494+ // Pre-set latch with a recent timestamp (2 minutes ago — within threshold)
495+ client [ 'lastApiCompletionTimestamp' ] = Date . now ( ) - 2 * 60 * 1000 ;
496+ client [ 'thinkingClearLatched' ] = true ;
497+
498+ const gen = client . sendMessageStream (
499+ [ { text : 'Hello' } ] ,
500+ new AbortController ( ) . signal ,
501+ 'prompt-3' ,
502+ { type : SendMessageType . UserQuery } ,
503+ ) ;
504+ for await ( const _ of gen ) {
505+ /* drain */
506+ }
507+
508+ expect ( client [ 'thinkingClearLatched' ] ) . toBe ( true ) ;
509+ expect ( mockChat . stripThoughtsFromHistoryKeepRecent ) . toHaveBeenCalledWith (
510+ 1 ,
511+ ) ;
512+ } ) ;
513+
514+ it ( 'should update lastApiCompletionTimestamp after API call' , async ( ) => {
515+ client [ 'lastApiCompletionTimestamp' ] = null ;
516+
517+ const before = Date . now ( ) ;
518+ const gen = client . sendMessageStream (
519+ [ { text : 'Hello' } ] ,
520+ new AbortController ( ) . signal ,
521+ 'prompt-4' ,
522+ { type : SendMessageType . UserQuery } ,
523+ ) ;
524+ for await ( const _ of gen ) {
525+ /* drain */
526+ }
527+
528+ expect ( client [ 'lastApiCompletionTimestamp' ] ) . toBeGreaterThanOrEqual (
529+ before ,
530+ ) ;
531+ } ) ;
532+
533+ it ( 'should reset latch and timestamp on resetChat' , async ( ) => {
534+ client [ 'lastApiCompletionTimestamp' ] = Date . now ( ) ;
535+ client [ 'thinkingClearLatched' ] = true ;
536+
537+ await client . resetChat ( ) ;
538+
539+ expect ( client [ 'thinkingClearLatched' ] ) . toBe ( false ) ;
540+ expect ( client [ 'lastApiCompletionTimestamp' ] ) . toBeNull ( ) ;
541+ } ) ;
542+ } ) ;
543+
430544 describe ( 'tryCompressChat' , ( ) => {
431545 const mockGetHistory = vi . fn ( ) ;
432546
@@ -436,6 +550,7 @@ describe('Gemini Client (client.ts)', () => {
436550 addHistory : vi . fn ( ) ,
437551 setHistory : vi . fn ( ) ,
438552 stripThoughtsFromHistory : vi . fn ( ) ,
553+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
439554 } as unknown as GeminiChat ;
440555 } ) ;
441556
@@ -457,6 +572,7 @@ describe('Gemini Client (client.ts)', () => {
457572 getHistory : vi . fn ( ( _curated ?: boolean ) => chatHistory ) ,
458573 setHistory : vi . fn ( ) ,
459574 stripThoughtsFromHistory : vi . fn ( ) ,
575+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
460576 } ;
461577 client [ 'chat' ] = mockOriginalChat as GeminiChat ;
462578
@@ -1149,6 +1265,7 @@ describe('Gemini Client (client.ts)', () => {
11491265 addHistory : vi . fn ( ) ,
11501266 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
11511267 stripThoughtsFromHistory : vi . fn ( ) ,
1268+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
11521269 } as unknown as GeminiChat ;
11531270 client [ 'chat' ] = mockChat ;
11541271
@@ -1204,6 +1321,7 @@ Other open files:
12041321 addHistory : vi . fn ( ) ,
12051322 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
12061323 stripThoughtsFromHistory : vi . fn ( ) ,
1324+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
12071325 } ;
12081326 client [ 'chat' ] = mockChat as GeminiChat ;
12091327
@@ -1260,6 +1378,7 @@ Other open files:
12601378 addHistory : vi . fn ( ) ,
12611379 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
12621380 stripThoughtsFromHistory : vi . fn ( ) ,
1381+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
12631382 } ;
12641383 client [ 'chat' ] = mockChat as GeminiChat ;
12651384
@@ -1326,6 +1445,7 @@ hello
13261445 addHistory : vi . fn ( ) ,
13271446 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
13281447 stripThoughtsFromHistory : vi . fn ( ) ,
1448+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
13291449 } ;
13301450 client [ 'chat' ] = mockChat as GeminiChat ;
13311451
@@ -1365,6 +1485,7 @@ Other open files:
13651485 addHistory : vi . fn ( ) ,
13661486 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
13671487 stripThoughtsFromHistory : vi . fn ( ) ,
1488+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
13681489 } ;
13691490 client [ 'chat' ] = mockChat as GeminiChat ;
13701491
@@ -1410,6 +1531,7 @@ Other open files:
14101531 addHistory : vi . fn ( ) ,
14111532 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
14121533 stripThoughtsFromHistory : vi . fn ( ) ,
1534+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
14131535 } ;
14141536 client [ 'chat' ] = mockChat as GeminiChat ;
14151537
@@ -1498,6 +1620,7 @@ Other open files:
14981620 addHistory : vi . fn ( ) ,
14991621 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
15001622 stripThoughtsFromHistory : vi . fn ( ) ,
1623+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
15011624 } ;
15021625 client [ 'chat' ] = mockChat as GeminiChat ;
15031626
@@ -1555,6 +1678,7 @@ Other open files:
15551678 addHistory : vi . fn ( ) ,
15561679 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
15571680 stripThoughtsFromHistory : vi . fn ( ) ,
1681+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
15581682 } ;
15591683 client [ 'chat' ] = mockChat as GeminiChat ;
15601684
@@ -1636,6 +1760,7 @@ Other open files:
16361760 { role : 'user' , parts : [ { text : 'previous message' } ] } ,
16371761 ] ) ,
16381762 stripThoughtsFromHistory : vi . fn ( ) ,
1763+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
16391764 } ;
16401765 client [ 'chat' ] = mockChat as GeminiChat ;
16411766 } ) ;
@@ -1889,6 +2014,7 @@ Other open files:
18892014 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) , // Default empty history
18902015 setHistory : vi . fn ( ) ,
18912016 stripThoughtsFromHistory : vi . fn ( ) ,
2017+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
18922018 } ;
18932019 client [ 'chat' ] = mockChat as GeminiChat ;
18942020
@@ -2228,6 +2354,7 @@ Other open files:
22282354 addHistory : vi . fn ( ) ,
22292355 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
22302356 stripThoughtsFromHistory : vi . fn ( ) ,
2357+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
22312358 } ;
22322359 client [ 'chat' ] = mockChat as GeminiChat ;
22332360
@@ -2265,6 +2392,7 @@ Other open files:
22652392 addHistory : vi . fn ( ) ,
22662393 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
22672394 stripThoughtsFromHistory : vi . fn ( ) ,
2395+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
22682396 } ;
22692397 client [ 'chat' ] = mockChat as GeminiChat ;
22702398
@@ -2305,6 +2433,7 @@ Other open files:
23052433 addHistory : vi . fn ( ) ,
23062434 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
23072435 stripThoughtsFromHistory : vi . fn ( ) ,
2436+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
23082437 } ;
23092438 client [ 'chat' ] = mockChat as GeminiChat ;
23102439
@@ -2329,6 +2458,7 @@ Other open files:
23292458 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
23302459 setHistory : vi . fn ( ) ,
23312460 stripThoughtsFromHistory : vi . fn ( ) ,
2461+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
23322462 stripOrphanedUserEntriesFromHistory : vi . fn ( ) ,
23332463 } ;
23342464 client [ 'chat' ] = mockChat as GeminiChat ;
@@ -2361,6 +2491,7 @@ Other open files:
23612491 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
23622492 setHistory : vi . fn ( ) ,
23632493 stripThoughtsFromHistory : vi . fn ( ) ,
2494+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
23642495 stripOrphanedUserEntriesFromHistory : vi . fn ( ) ,
23652496 } ;
23662497 client [ 'chat' ] = mockChat as GeminiChat ;
@@ -2405,6 +2536,7 @@ Other open files:
24052536 addHistory : vi . fn ( ) ,
24062537 getHistory : vi . fn ( ) . mockReturnValue ( [ ] ) ,
24072538 stripThoughtsFromHistory : vi . fn ( ) ,
2539+ stripThoughtsFromHistoryKeepRecent : vi . fn ( ) ,
24082540 } ;
24092541 client [ 'chat' ] = mockChat as GeminiChat ;
24102542 } ) ;
0 commit comments