@@ -34,7 +34,10 @@ import { ShellTool } from '../tools/shell.js';
3434import { ReadFileTool } from '../tools/read-file.js' ;
3535import { GrepTool } from '../tools/grep.js' ;
3636import { RipGrepTool , canUseRipgrep } from '../tools/ripGrep.js' ;
37- import { logRipgrepFallback } from '../telemetry/loggers.js' ;
37+ import {
38+ logRipgrepFallback ,
39+ logApprovalModeDuration ,
40+ } from '../telemetry/loggers.js' ;
3841import { RipgrepFallbackEvent } from '../telemetry/types.js' ;
3942import { ToolRegistry } from '../tools/tool-registry.js' ;
4043import { ACTIVATE_SKILL_TOOL_NAME } from '../tools/tool-names.js' ;
@@ -131,6 +134,7 @@ vi.mock('../telemetry/loggers.js', async (importOriginal) => {
131134 return {
132135 ...actual ,
133136 logRipgrepFallback : vi . fn ( ) ,
137+ logApprovalModeDuration : vi . fn ( ) ,
134138 } ;
135139} ) ;
136140
@@ -1419,6 +1423,84 @@ describe('setApprovalMode with folder trust', () => {
14191423 expect ( updateSpy ) . not . toHaveBeenCalled ( ) ;
14201424 } ) ;
14211425
1426+ describe ( 'approval mode duration logging' , ( ) => {
1427+ beforeEach ( ( ) => {
1428+ vi . mocked ( logApprovalModeDuration ) . mockClear ( ) ;
1429+ } ) ;
1430+
1431+ it ( 'should initialize lastModeSwitchTime with performance.now() and log positive duration' , ( ) => {
1432+ const startTime = 1000 ;
1433+ const endTime = 5000 ;
1434+ const performanceSpy = vi . spyOn ( performance , 'now' ) ;
1435+
1436+ performanceSpy . mockReturnValueOnce ( startTime ) ;
1437+ const config = new Config ( baseParams ) ;
1438+ vi . spyOn ( config , 'isTrustedFolder' ) . mockReturnValue ( true ) ;
1439+
1440+ performanceSpy . mockReturnValueOnce ( endTime ) ;
1441+ config . setApprovalMode ( ApprovalMode . PLAN ) ;
1442+
1443+ expect ( logApprovalModeDuration ) . toHaveBeenCalledWith (
1444+ config ,
1445+ expect . objectContaining ( {
1446+ mode : ApprovalMode . DEFAULT ,
1447+ duration_ms : endTime - startTime ,
1448+ } ) ,
1449+ ) ;
1450+ performanceSpy . mockRestore ( ) ;
1451+ } ) ;
1452+
1453+ it ( 'should skip logging if duration is zero or negative' , ( ) => {
1454+ const startTime = 5000 ;
1455+ const endTime = 4000 ;
1456+ const performanceSpy = vi . spyOn ( performance , 'now' ) ;
1457+
1458+ performanceSpy . mockReturnValueOnce ( startTime ) ;
1459+ const config = new Config ( baseParams ) ;
1460+ vi . spyOn ( config , 'isTrustedFolder' ) . mockReturnValue ( true ) ;
1461+
1462+ performanceSpy . mockReturnValueOnce ( endTime ) ;
1463+ config . setApprovalMode ( ApprovalMode . PLAN ) ;
1464+
1465+ expect ( logApprovalModeDuration ) . not . toHaveBeenCalled ( ) ;
1466+ performanceSpy . mockRestore ( ) ;
1467+ } ) ;
1468+
1469+ it ( 'should update lastModeSwitchTime after logging to prevent double counting' , ( ) => {
1470+ const time1 = 1000 ;
1471+ const time2 = 3000 ;
1472+ const time3 = 6000 ;
1473+ const performanceSpy = vi . spyOn ( performance , 'now' ) ;
1474+
1475+ performanceSpy . mockReturnValueOnce ( time1 ) ;
1476+ const config = new Config ( baseParams ) ;
1477+ vi . spyOn ( config , 'isTrustedFolder' ) . mockReturnValue ( true ) ;
1478+
1479+ performanceSpy . mockReturnValueOnce ( time2 ) ;
1480+ config . setApprovalMode ( ApprovalMode . PLAN ) ;
1481+ expect ( logApprovalModeDuration ) . toHaveBeenCalledWith (
1482+ config ,
1483+ expect . objectContaining ( {
1484+ mode : ApprovalMode . DEFAULT ,
1485+ duration_ms : time2 - time1 ,
1486+ } ) ,
1487+ ) ;
1488+
1489+ vi . mocked ( logApprovalModeDuration ) . mockClear ( ) ;
1490+
1491+ performanceSpy . mockReturnValueOnce ( time3 ) ;
1492+ config . setApprovalMode ( ApprovalMode . YOLO ) ;
1493+ expect ( logApprovalModeDuration ) . toHaveBeenCalledWith (
1494+ config ,
1495+ expect . objectContaining ( {
1496+ mode : ApprovalMode . PLAN ,
1497+ duration_ms : time3 - time2 ,
1498+ } ) ,
1499+ ) ;
1500+ performanceSpy . mockRestore ( ) ;
1501+ } ) ;
1502+ } ) ;
1503+
14221504 describe ( 'registerCoreTools' , ( ) => {
14231505 beforeEach ( ( ) => {
14241506 vi . clearAllMocks ( ) ;
0 commit comments