@@ -75,105 +75,84 @@ export async function sendUnifiedNotification(
7575}
7676
7777function buildMinimalMessage ( data : NotificationData ) : string {
78- const hasAiPruning = data . aiPrunedCount > 0
79- const hasGcActivity = data . gcPending && data . gcPending . toolsDeduped > 0
78+ const { justNowTokens, totalTokens } = calculateStats ( data )
8079
81- if ( hasAiPruning ) {
82- const gcTokens = hasGcActivity ? data . gcPending ! . tokensCollected : 0
83- const totalSaved = formatTokenCount ( data . aiTokensSaved + gcTokens )
84- const toolText = data . aiPrunedCount === 1 ? 'tool' : 'tools'
85-
86- let cycleStats = `${ data . aiPrunedCount } ${ toolText } `
87- if ( hasGcActivity ) {
88- cycleStats += `, ~${ formatTokenCount ( data . gcPending ! . tokensCollected ) } 🗑️`
89- }
80+ return formatStatsHeader ( totalTokens , justNowTokens )
81+ }
9082
91- let message = `🧹 DCP: ~${ totalSaved } saved (${ cycleStats } )`
92- message += buildSessionSuffix ( data . sessionStats , data . aiPrunedCount )
83+ function calculateStats ( data : NotificationData ) : {
84+ justNowTokens : number
85+ totalTokens : number
86+ } {
87+ // "Just now" = AI pruning + pending GC from this notification cycle
88+ const justNowTokens = data . aiTokensSaved + ( data . gcPending ?. tokensCollected ?? 0 )
9389
94- return message
95- } else {
96- const tokensCollected = formatTokenCount ( data . gcPending ! . tokensCollected )
90+ // Session stats are updated BEFORE notification is sent, so they already include
91+ // the current cycle's values (totalTokensSaved includes current cycle, totalGCTokens includes current cycle)
92+ const totalTokens = data . sessionStats
93+ ? data . sessionStats . totalTokensSaved + data . sessionStats . totalGCTokens
94+ : justNowTokens
9795
98- let message = `🗑️ DCP: ~ ${ tokensCollected } collected`
99- message += buildSessionSuffix ( data . sessionStats , 0 )
96+ return { justNowTokens , totalTokens }
97+ }
10098
101- return message
102- }
99+ function formatStatsHeader (
100+ totalTokens : number ,
101+ justNowTokens : number
102+ ) : string {
103+ // Format token counts (formatTokenCount already includes "tokens" suffix)
104+ const totalTokensStr = `~${ formatTokenCount ( totalTokens ) } `
105+ const justNowTokensStr = `~${ formatTokenCount ( justNowTokens ) } `
106+
107+ // Pad token strings to align columns
108+ const maxTokenLen = Math . max ( totalTokensStr . length , justNowTokensStr . length )
109+ const totalTokensPadded = totalTokensStr . padStart ( maxTokenLen )
110+ const justNowTokensPadded = justNowTokensStr . padStart ( maxTokenLen )
111+
112+ return [
113+ `▣ DCP Stats` ,
114+ ` Total saved │ ${ totalTokensPadded } ` ,
115+ ` Just now │ ${ justNowTokensPadded } ` ,
116+ ] . join ( '\n' )
103117}
104118
105119function buildDetailedMessage ( data : NotificationData , workingDirectory ?: string ) : string {
106- const hasAiPruning = data . aiPrunedCount > 0
107- const hasGcActivity = data . gcPending && data . gcPending . toolsDeduped > 0
108-
109- let message : string
110-
111- if ( hasAiPruning ) {
112- const gcTokens = hasGcActivity ? data . gcPending ! . tokensCollected : 0
113- const totalSaved = formatTokenCount ( data . aiTokensSaved + gcTokens )
114- const toolText = data . aiPrunedCount === 1 ? 'tool' : 'tools'
115-
116- let cycleStats = `${ data . aiPrunedCount } ${ toolText } `
117- if ( hasGcActivity ) {
118- cycleStats += `, ~${ formatTokenCount ( data . gcPending ! . tokensCollected ) } 🗑️`
119- }
120+ const { justNowTokens, totalTokens } = calculateStats ( data )
120121
121- message = `🧹 DCP: ~${ totalSaved } saved (${ cycleStats } )`
122- message += buildSessionSuffix ( data . sessionStats , data . aiPrunedCount )
123- message += '\n'
122+ let message = formatStatsHeader ( totalTokens , justNowTokens )
124123
125- message += `\n🤖 LLM analysis (${ data . aiPrunedIds . length } ):\n`
126- const toolsSummary = buildToolsSummary ( data . aiPrunedIds , data . toolMetadata , workingDirectory )
127-
128- for ( const [ toolName , params ] of toolsSummary . entries ( ) ) {
129- if ( params . length > 0 ) {
130- message += ` ${ toolName } (${ params . length } ):\n`
131- for ( const param of params ) {
132- message += ` ${ param } \n`
124+ // Add tool breakdown if there was AI pruning
125+ if ( data . aiPrunedCount > 0 ) {
126+ message += '\n\n▣ Pruned tools:'
127+
128+ for ( const prunedId of data . aiPrunedIds ) {
129+ const normalizedId = prunedId . toLowerCase ( )
130+ const metadata = data . toolMetadata . get ( normalizedId )
131+
132+ if ( metadata ) {
133+ const paramKey = extractParameterKey ( metadata )
134+ if ( paramKey ) {
135+ const displayKey = truncate ( shortenPath ( paramKey , workingDirectory ) , 60 )
136+ message += `\n→ ${ metadata . tool } : ${ displayKey } `
137+ } else {
138+ message += `\n→ ${ metadata . tool } `
133139 }
134140 }
135141 }
136142
137- const foundToolNames = new Set ( toolsSummary . keys ( ) )
138- const missingTools = data . aiPrunedIds . filter ( id => {
139- const normalizedId = id . toLowerCase ( )
140- const metadata = data . toolMetadata . get ( normalizedId )
141- return ! metadata || ! foundToolNames . has ( metadata . tool )
142- } )
143+ const knownCount = data . aiPrunedIds . filter ( id =>
144+ data . toolMetadata . has ( id . toLowerCase ( ) )
145+ ) . length
146+ const unknownCount = data . aiPrunedIds . length - knownCount
143147
144- if ( missingTools . length > 0 ) {
145- message += ` (${ missingTools . length } tool${ missingTools . length > 1 ? 's' : '' } with unknown metadata)\n `
148+ if ( unknownCount > 0 ) {
149+ message += `\n→ (${ unknownCount } tool${ unknownCount > 1 ? 's' : '' } with unknown metadata)`
146150 }
147- } else {
148- const tokensCollected = formatTokenCount ( data . gcPending ! . tokensCollected )
149-
150- message = `🗑️ DCP: ~${ tokensCollected } collected`
151- message += buildSessionSuffix ( data . sessionStats , 0 )
152151 }
153152
154153 return message . trim ( )
155154}
156155
157- function buildSessionSuffix ( sessionStats : SessionStats | null , currentAiPruned : number ) : string {
158- if ( ! sessionStats ) {
159- return ''
160- }
161-
162- if ( sessionStats . totalToolsPruned <= currentAiPruned ) {
163- return ''
164- }
165-
166- const totalSaved = sessionStats . totalTokensSaved + sessionStats . totalGCTokens
167- let suffix = ` │ Session: ~${ formatTokenCount ( totalSaved ) } (${ sessionStats . totalToolsPruned } tools`
168-
169- if ( sessionStats . totalGCTokens > 0 ) {
170- suffix += `, ~${ formatTokenCount ( sessionStats . totalGCTokens ) } 🗑️`
171- }
172-
173- suffix += ')'
174- return suffix
175- }
176-
177156export function formatPruningResultForTool (
178157 result : PruningResult ,
179158 workingDirectory ?: string
0 commit comments