* Process session files and extract stats. * Can filter by date range.
(
sessionFiles: string[],
options: ProcessOptions = {},
)
| 115 | * Can filter by date range. |
| 116 | */ |
| 117 | async function processSessionFiles( |
| 118 | sessionFiles: string[], |
| 119 | options: ProcessOptions = {}, |
| 120 | ): Promise<ProcessedStats> { |
| 121 | const { fromDate, toDate } = options |
| 122 | const fs = getFsImplementation() |
| 123 | |
| 124 | const dailyActivityMap = new Map<string, DailyActivity>() |
| 125 | const dailyModelTokensMap = new Map<string, { [modelName: string]: number }>() |
| 126 | const sessions: SessionStats[] = [] |
| 127 | const hourCounts = new Map<number, number>() |
| 128 | let totalMessages = 0 |
| 129 | let totalSpeculationTimeSavedMs = 0 |
| 130 | const modelUsageAgg: { [modelName: string]: ModelUsage } = {} |
| 131 | const shotDistributionMap = feature('SHOT_STATS') |
| 132 | ? new Map<number, number>() |
| 133 | : undefined |
| 134 | // Track parent sessions that already recorded a shot count (dedup across subagents) |
| 135 | const sessionsWithShotCount = new Set<string>() |
| 136 | |
| 137 | // Process session files in parallel batches for better performance |
| 138 | const BATCH_SIZE = 20 |
| 139 | for (let i = 0; i < sessionFiles.length; i += BATCH_SIZE) { |
| 140 | const batch = sessionFiles.slice(i, i + BATCH_SIZE) |
| 141 | const results = await Promise.all( |
| 142 | batch.map(async sessionFile => { |
| 143 | try { |
| 144 | // If we have a fromDate filter, skip files that haven't been modified since then |
| 145 | if (fromDate) { |
| 146 | let fileSize = 0 |
| 147 | try { |
| 148 | const fileStat = await fs.stat(sessionFile) |
| 149 | const fileModifiedDate = toDateString(fileStat.mtime) |
| 150 | if (isDateBefore(fileModifiedDate, fromDate)) { |
| 151 | return { |
| 152 | sessionFile, |
| 153 | entries: null, |
| 154 | error: null, |
| 155 | skipped: true, |
| 156 | } |
| 157 | } |
| 158 | fileSize = fileStat.size |
| 159 | } catch { |
| 160 | // If we can't stat the file, try to read it anyway |
| 161 | } |
| 162 | // For large files, peek at the session start date before reading everything. |
| 163 | // Sessions that pass the mtime filter but started before fromDate are skipped |
| 164 | // (e.g. a month-old session resumed today gets a new mtime write but old start date). |
| 165 | if (fileSize > 65536) { |
| 166 | const startDate = await readSessionStartDate(sessionFile) |
| 167 | if (startDate && isDateBefore(startDate, fromDate)) { |
| 168 | return { |
| 169 | sessionFile, |
| 170 | entries: null, |
| 171 | error: null, |
| 172 | skipped: true, |
| 173 | } |
| 174 | } |
no test coverage detected