( sessionId: string, lite: LiteSessionFile, projectPath?: string, )
| 77 | * Exported for reuse by getSessionInfoImpl. |
| 78 | */ |
| 79 | export function parseSessionInfoFromLite( |
| 80 | sessionId: string, |
| 81 | lite: LiteSessionFile, |
| 82 | projectPath?: string, |
| 83 | ): SessionInfo | null { |
| 84 | const { head, tail, mtime, size } = lite |
| 85 | |
| 86 | // Check first line for sidechain sessions |
| 87 | const firstNewline = head.indexOf('\n') |
| 88 | const firstLine = firstNewline >= 0 ? head.slice(0, firstNewline) : head |
| 89 | if ( |
| 90 | firstLine.includes('"isSidechain":true') || |
| 91 | firstLine.includes('"isSidechain": true') |
| 92 | ) { |
| 93 | return null |
| 94 | } |
| 95 | // User title (customTitle) wins over AI title (aiTitle); distinct |
| 96 | // field names mean extractLastJsonStringField naturally disambiguates. |
| 97 | const customTitle = |
| 98 | extractLastJsonStringField(tail, 'customTitle') || |
| 99 | extractLastJsonStringField(head, 'customTitle') || |
| 100 | extractLastJsonStringField(tail, 'aiTitle') || |
| 101 | extractLastJsonStringField(head, 'aiTitle') || |
| 102 | undefined |
| 103 | const firstPrompt = extractFirstPromptFromHead(head) || undefined |
| 104 | // First entry's ISO timestamp → epoch ms. More reliable than |
| 105 | // stat().birthtime which is unsupported on some filesystems. |
| 106 | const firstTimestamp = extractJsonStringField(head, 'timestamp') |
| 107 | let createdAt: number | undefined |
| 108 | if (firstTimestamp) { |
| 109 | const parsed = Date.parse(firstTimestamp) |
| 110 | if (!Number.isNaN(parsed)) createdAt = parsed |
| 111 | } |
| 112 | // last-prompt tail entry (captured by extractFirstPrompt at write |
| 113 | // time, filtered) shows what the user was most recently doing. |
| 114 | // Head scan is fallback for sessions without a last-prompt entry. |
| 115 | const summary = |
| 116 | customTitle || |
| 117 | extractLastJsonStringField(tail, 'lastPrompt') || |
| 118 | extractLastJsonStringField(tail, 'summary') || |
| 119 | firstPrompt |
| 120 | |
| 121 | // Skip metadata-only sessions (no title, no summary, no prompt) |
| 122 | if (!summary) return null |
| 123 | const gitBranch = |
| 124 | extractLastJsonStringField(tail, 'gitBranch') || |
| 125 | extractJsonStringField(head, 'gitBranch') || |
| 126 | undefined |
| 127 | const sessionCwd = |
| 128 | extractJsonStringField(head, 'cwd') || projectPath || undefined |
| 129 | // Type-scope tag extraction to the {"type":"tag"} JSONL line to avoid |
| 130 | // collision with tool_use inputs containing a `tag` parameter (git tag, |
| 131 | // Docker tags, cloud resource tags). Mirrors sessionStorage.ts:608. |
| 132 | const tagLine = tail.split('\n').findLast(l => l.startsWith('{"type":"tag"')) |
| 133 | const tag = tagLine |
| 134 | ? extractLastJsonStringField(tagLine, 'tag') || undefined |
| 135 | : undefined |
| 136 |
no test coverage detected