diff --git a/src/data.js b/src/data.js index 4c08748..3f0a5ca 100644 --- a/src/data.js +++ b/src/data.js @@ -729,6 +729,17 @@ function normalizePiUsage(usage) { const SAFE_PI_SESSION_ID = /^[A-Za-z0-9._-]{1,128}$/; +function findPiSessionHeaderLine(lines) { + for (let i = 0; i < Math.min(lines.length, 50); i++) { + try { + const entry = JSON.parse(lines[i]); + if (entry && entry.type === 'session' && entry.id) return { header: entry, line: i }; + } catch {} + } + return { header: null, line: -1 }; +} + + function parsePiSessionFile(sessionFile) { if (!fs.existsSync(sessionFile)) return null; @@ -742,18 +753,9 @@ function parsePiSessionFile(sessionFile) { } if (lines.length === 0) return null; - let header; - let headerLine = -1; - for (let i = 0; i < Math.min(lines.length, 50); i++) { - try { - const entry = JSON.parse(lines[i]); - if (entry && entry.type === 'session' && entry.id) { - header = entry; - headerLine = i; - break; - } - } catch {} - } + const foundHeader = findPiSessionHeaderLine(lines); + const header = foundHeader.header; + const headerLine = foundHeader.line; if (!header) return null; let sessionId = String(header.id); @@ -844,7 +846,8 @@ function loadPiDetail(sessionId, filePath, options) { let lines; try { lines = readLines(filePath); } catch { return { messages }; } - for (let i = 1; i < lines.length; i++) { + const headerLine = findPiSessionHeaderLine(lines).line; + for (let i = Math.max(headerLine + 1, 1); i < lines.length; i++) { if (maxMessages && messages.length >= maxMessages) break; try { const entry = JSON.parse(lines[i]); diff --git a/test/pi-session.test.js b/test/pi-session.test.js index e2bff74..0a0d7cb 100644 --- a/test/pi-session.test.js +++ b/test/pi-session.test.js @@ -130,6 +130,20 @@ test('loadPiDetail returns role-compatible display messages with tokens', () => assert.equal(detail.messages[1].tokens.cacheCreateTokens, 4); }); +test('loadPiDetail starts messages after delayed OMP session header', () => { + const dir = tmpDir(); + const file = path.join(dir, 'sessions', '--tmp--project--', '2026_pi-session-detail-title.jsonl'); + writeJsonl(file, [ + { type: 'title', title: 'brief-improve' }, + { type: 'session', id: 'pi-session-detail-title', cwd: '/tmp/project', timestamp: '2026-05-24T10:00:00.000Z' }, + { type: 'message', timestamp: '2026-05-24T10:00:01.000Z', message: { role: 'user', content: 'hello' } }, + ]); + + const detail = loadPiDetail('pi-session-detail-title', file, { maxMessages: 10 }); + assert.equal(detail.messages.length, 1); + assert.equal(detail.messages[0].content, 'hello'); +}); + test('scanPiSessions ignores malformed and non-OMP files', () => { const agentDir = tmpDir(); const valid = path.join(agentDir, 'sessions', '--tmp--project--', '2026_pi-session-3.jsonl');