|
1 | | -import * as vscode from 'vscode'; |
| 1 | +import * as vscode from 'vscode'; |
2 | 2 | import * as fs from 'fs'; |
3 | 3 | import * as path from 'path'; |
4 | 4 | import * as os from 'os'; |
@@ -161,7 +161,7 @@ type LocalViewRegressionCase = { |
161 | 161 |
|
162 | 162 | class CopilotTokenTracker implements vscode.Disposable { |
163 | 163 | // Cache version - increment this when making changes that require cache invalidation |
164 | | - private static readonly CACHE_VERSION = 39; // Cache-aware cost: track cachedReadTokens/cacheCreationTokens in ModelUsage |
| 164 | + private static readonly CACHE_VERSION = 40; // Fix lastInteraction: use content timestamps only, not max(timestamp, mtime) |
165 | 165 | // Maximum length for displaying workspace IDs in diagnostics/customization matrix |
166 | 166 | private static readonly WORKSPACE_ID_DISPLAY_LENGTH = 8; |
167 | 167 |
|
@@ -2161,7 +2161,17 @@ class CopilotTokenTracker implements vscode.Disposable { |
2161 | 2161 | continue; |
2162 | 2162 | } |
2163 | 2163 |
|
| 2164 | + // Derive lastActivity from session content timestamp (not mtime) to avoid |
| 2165 | + // date mis-classification when VS Code writes the file after midnight. |
| 2166 | + const lastActivity = sessionData.lastInteraction |
| 2167 | + ? new Date(sessionData.lastInteraction) |
| 2168 | + : new Date(mtime); |
| 2169 | + |
2164 | 2170 | // Add to last 30 days stats |
| 2171 | + if (lastActivity < last30DaysStart) { |
| 2172 | + processed++; |
| 2173 | + continue; |
| 2174 | + } |
2165 | 2175 | last30DaysStats.sessions++; |
2166 | 2176 | this.mergeUsageAnalysis(last30DaysStats, analysis); |
2167 | 2177 |
|
@@ -2196,14 +2206,14 @@ class CopilotTokenTracker implements vscode.Disposable { |
2196 | 2206 | } |
2197 | 2207 | } |
2198 | 2208 |
|
2199 | | - // Add to month stats if modified this calendar month |
2200 | | - if (mtime >= monthStart.getTime()) { |
| 2209 | + // Add to month stats if activity falls in this calendar month |
| 2210 | + if (lastActivity >= monthStart) { |
2201 | 2211 | monthStats.sessions++; |
2202 | 2212 | this.mergeUsageAnalysis(monthStats, analysis); |
2203 | 2213 | } |
2204 | 2214 |
|
2205 | | - // Add to today stats if modified today |
2206 | | - if (mtime >= todayStart.getTime()) { |
| 2215 | + // Add to today stats if activity falls today |
| 2216 | + if (lastActivity >= todayStart) { |
2207 | 2217 | todayStats.sessions++; |
2208 | 2218 | this.mergeUsageAnalysis(todayStats, analysis); |
2209 | 2219 | } |
@@ -2826,18 +2836,11 @@ class CopilotTokenTracker implements vscode.Disposable { |
2826 | 2836 | return undefined; |
2827 | 2837 | } |
2828 | 2838 |
|
2829 | | - // Determine lastInteraction: use the more recent of cached timestamp or file mtime |
2830 | | - // This handles cases where file was modified but content timestamps are older |
2831 | | - let lastInteraction: string | null = cached.lastInteraction || null; |
2832 | | - if (lastInteraction) { |
2833 | | - const cachedLastInteraction = new Date(lastInteraction); |
2834 | | - if (stat.mtime > cachedLastInteraction) { |
2835 | | - lastInteraction = stat.mtime.toISOString(); |
2836 | | - } |
2837 | | - } else { |
2838 | | - // No cached lastInteraction, use file mtime |
2839 | | - lastInteraction = stat.mtime.toISOString(); |
2840 | | - } |
| 2839 | + // Use the cached lastInteraction from session content directly. |
| 2840 | + // Do NOT fall back to file mtime here: mtime is updated whenever VS Code writes the |
| 2841 | + // session file (e.g. finalising a session just after midnight), which would shift |
| 2842 | + // yesterday's sessions into "today". Only use mtime when no content timestamp exists. |
| 2843 | + const lastInteraction: string | null = cached.lastInteraction || stat.mtime.toISOString(); |
2841 | 2844 |
|
2842 | 2845 | // Reconstruct SessionFileDetails from cache. |
2843 | 2846 | // Prefer actualTokens (real API count) when available; fall back to estimated tokens. |
@@ -3049,10 +3052,10 @@ class CopilotTokenTracker implements vscode.Disposable { |
3049 | 3052 | if (timestamps.length > 0) { |
3050 | 3053 | timestamps.sort((a, b) => a - b); |
3051 | 3054 | details.firstInteraction = new Date(timestamps[0]).toISOString(); |
3052 | | - const lastTimestamp = new Date(timestamps[timestamps.length - 1]); |
3053 | | - details.lastInteraction = lastTimestamp > stat.mtime |
3054 | | - ? lastTimestamp.toISOString() |
3055 | | - : stat.mtime.toISOString(); |
| 3055 | + // Use the last content timestamp directly. Do NOT mix in stat.mtime: mtime is set |
| 3056 | + // when VS Code writes the file (e.g. after midnight), which would shift yesterday's |
| 3057 | + // session into 'today', breaking the 30-day/today cutoff boundaries. |
| 3058 | + details.lastInteraction = new Date(timestamps[timestamps.length - 1]).toISOString(); |
3056 | 3059 | } else { |
3057 | 3060 | details.lastInteraction = stat.mtime.toISOString(); |
3058 | 3061 | } |
@@ -3116,12 +3119,10 @@ class CopilotTokenTracker implements vscode.Disposable { |
3116 | 3119 | if (timestamps.length > 0) { |
3117 | 3120 | timestamps.sort((a, b) => a - b); |
3118 | 3121 | details.firstInteraction = new Date(timestamps[0]).toISOString(); |
3119 | | - // Use the more recent of: extracted last timestamp OR file modification time |
3120 | | - // This handles cases where new requests are added without timestamp fields |
3121 | | - const lastTimestamp = new Date(timestamps[timestamps.length - 1]); |
3122 | | - details.lastInteraction = lastTimestamp > stat.mtime |
3123 | | - ? lastTimestamp.toISOString() |
3124 | | - : stat.mtime.toISOString(); |
| 3122 | + // Use the last content timestamp directly. Do NOT mix in stat.mtime: mtime is set |
| 3123 | + // when VS Code writes the file (e.g. after midnight), which would shift yesterday's |
| 3124 | + // session into 'today', breaking the 30-day/today cutoff boundaries. |
| 3125 | + details.lastInteraction = new Date(timestamps[timestamps.length - 1]).toISOString(); |
3125 | 3126 | } else { |
3126 | 3127 | // Fallback to file modification time if no timestamps in content |
3127 | 3128 | details.lastInteraction = stat.mtime.toISOString(); |
@@ -3191,12 +3192,10 @@ class CopilotTokenTracker implements vscode.Disposable { |
3191 | 3192 | if (timestamps.length > 0) { |
3192 | 3193 | timestamps.sort((a, b) => a - b); |
3193 | 3194 | details.firstInteraction = new Date(timestamps[0]).toISOString(); |
3194 | | - // Use the more recent of: extracted last timestamp OR file modification time |
3195 | | - // This handles cases where new requests are added without timestamp fields |
3196 | | - const lastTimestamp = new Date(timestamps[timestamps.length - 1]); |
3197 | | - details.lastInteraction = lastTimestamp > stat.mtime |
3198 | | - ? lastTimestamp.toISOString() |
3199 | | - : stat.mtime.toISOString(); |
| 3195 | + // Use the last content timestamp directly. Do NOT mix in stat.mtime: mtime is set |
| 3196 | + // when VS Code writes the file (e.g. after midnight), which would shift yesterday's |
| 3197 | + // session into 'today', breaking the 30-day/today cutoff boundaries. |
| 3198 | + details.lastInteraction = new Date(timestamps[timestamps.length - 1]).toISOString(); |
3200 | 3199 | } else { |
3201 | 3200 | // Fallback to file modification time if no timestamps in content |
3202 | 3201 | details.lastInteraction = stat.mtime.toISOString(); |
|
0 commit comments