(
chatsDir: string,
currentSessionId?: string,
options: GetSessionOptions = {},
)
| 235 | * @returns Array of session file entries, with sessionInfo null for corrupted files |
| 236 | */ |
| 237 | export const getAllSessionFiles = async ( |
| 238 | chatsDir: string, |
| 239 | currentSessionId?: string, |
| 240 | options: GetSessionOptions = {}, |
| 241 | ): Promise<SessionFileEntry[]> => { |
| 242 | try { |
| 243 | const files = await fs.readdir(chatsDir); |
| 244 | const sessionFiles = files |
| 245 | .filter( |
| 246 | (f) => |
| 247 | f.startsWith(SESSION_FILE_PREFIX) && |
| 248 | (f.endsWith('.json') || f.endsWith('.jsonl')), |
| 249 | ) |
| 250 | .sort(); // Sort by filename, which includes timestamp |
| 251 | |
| 252 | const sessionPromises = sessionFiles.map( |
| 253 | async (file): Promise<SessionFileEntry> => { |
| 254 | const filePath = path.join(chatsDir, file); |
| 255 | try { |
| 256 | const content = await loadConversationRecord(filePath, { |
| 257 | metadataOnly: !options.includeFullContent, |
| 258 | }); |
| 259 | if (!content) { |
| 260 | return { fileName: file, sessionInfo: null }; |
| 261 | } |
| 262 | |
| 263 | // Validate required fields |
| 264 | if (!content.sessionId) { |
| 265 | // Missing required fields - treat as corrupted |
| 266 | return { fileName: file, sessionInfo: null }; |
| 267 | } |
| 268 | |
| 269 | const fileTimestamp = |
| 270 | !content.startTime || !content.lastUpdated |
| 271 | ? ( |
| 272 | await fs.stat(filePath).catch(() => undefined) |
| 273 | )?.mtime.toISOString() |
| 274 | : undefined; |
| 275 | const fallbackTimestamp = fileTimestamp ?? new Date().toISOString(); |
| 276 | const startTime = |
| 277 | content.startTime || content.lastUpdated || fallbackTimestamp; |
| 278 | const lastUpdated = |
| 279 | content.lastUpdated || content.startTime || fallbackTimestamp; |
| 280 | |
| 281 | // Skip sessions with no resumable conversation content, including |
| 282 | // startup-only, system-only, command-only, and internal-context-only |
| 283 | // sessions. |
| 284 | if (!content.hasResumableContent) { |
| 285 | return { fileName: file, sessionInfo: null }; |
| 286 | } |
| 287 | |
| 288 | // Skip subagent sessions - these are implementation details of a tool call |
| 289 | // and shouldn't be surfaced for resumption in the main agent history. |
| 290 | if (content.kind === 'subagent') { |
| 291 | return { fileName: file, sessionInfo: null }; |
| 292 | } |
| 293 | |
| 294 | const firstUserMessage = content.firstUserMessage |
no test coverage detected