(options: ScanOptions = {})
| 310 | } |
| 311 | |
| 312 | async scanSessions(options: ScanOptions = {}): Promise<ScannedSession[]> { |
| 313 | const results: ScannedSession[] = []; |
| 314 | |
| 315 | for (const {filePath, sessionId} of walkCodexSessions(options.since)) { |
| 316 | let stats; |
| 317 | try { |
| 318 | stats = statSync(filePath); |
| 319 | } catch { |
| 320 | continue; |
| 321 | } |
| 322 | |
| 323 | if (options.since && stats.mtimeMs < options.since) continue; |
| 324 | |
| 325 | results.push({ |
| 326 | sessionId, |
| 327 | agent: this.agentName, |
| 328 | dir: '', // populated below from session_meta |
| 329 | title: null, |
| 330 | filePath, |
| 331 | fileSizeBytes: stats.size, |
| 332 | createdAt: stats.birthtimeMs || stats.mtimeMs, |
| 333 | updatedAt: stats.mtimeMs, |
| 334 | turnCount: 0, |
| 335 | }); |
| 336 | } |
| 337 | |
| 338 | results.sort((a, b) => b.updatedAt - a.updatedAt); |
| 339 | |
| 340 | // Populate title + dir from session_meta (first line) in parallel |
| 341 | await Promise.all(results.map(async (s) => { |
| 342 | const first = await readFirstLine(s.filePath); |
| 343 | if (first?.type === 'session_meta') { |
| 344 | const payload = first.payload as any; |
| 345 | s.dir = payload?.cwd ?? ''; |
| 346 | } |
| 347 | s.title = await this.extractTitle(s.filePath); |
| 348 | })); |
| 349 | |
| 350 | // Apply dir filter after reading cwd from session_meta |
| 351 | if (options.dir) { |
| 352 | return results.filter((s) => s.dir === options.dir); |
| 353 | } |
| 354 | |
| 355 | return results; |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | export const codexReader = new CodexReader(); |
no test coverage detected