From 0830d940dfda220acf7020037c99ff6050ff2b96 Mon Sep 17 00:00:00 2001 From: "Tim.Seriakov" Date: Fri, 3 Jul 2026 10:44:00 +0300 Subject: [PATCH] fix(pi): parse title-before-session OMP logs --- src/data.js | 27 ++++++++++++++++++++++++--- test/pi-session.test.js | 14 ++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/data.js b/src/data.js index eac627f..8b47316 100644 --- a/src/data.js +++ b/src/data.js @@ -743,13 +743,34 @@ function parsePiSessionFile(sessionFile) { if (lines.length === 0) return null; let header; - try { header = JSON.parse(lines[0]); } catch { return null; } - if (!header || header.type !== 'session' || !header.id) return null; + 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 {} + } + if (!header) return null; let sessionId = String(header.id); if (!SAFE_PI_SESSION_ID.test(sessionId)) return null; let projectPath = typeof header.cwd === 'string' ? header.cwd : ''; let title = typeof header.title === 'string' ? header.title.trim().slice(0, 200) : ''; + if (!title) { + for (let i = 0; i < headerLine; i++) { + try { + const entry = JSON.parse(lines[i]); + if (entry && entry.type === 'title' && typeof entry.title === 'string' && entry.title.trim()) { + title = entry.title.trim().slice(0, 200); + break; + } + } catch {} + } + } let msgCount = 0; let userMsgCount = 0; let firstMsg = ''; @@ -761,7 +782,7 @@ function parsePiSessionFile(sessionFile) { let hasUsage = false; let explicitCost = false; - for (let i = 1; i < lines.length; i++) { + for (let i = headerLine + 1; i < lines.length; i++) { try { const entry = JSON.parse(lines[i]); const ts = parseTimestamp(entry.timestamp || entry.ts); diff --git a/test/pi-session.test.js b/test/pi-session.test.js index bc31295..e2bff74 100644 --- a/test/pi-session.test.js +++ b/test/pi-session.test.js @@ -84,6 +84,20 @@ test('parsePiSessionFile reads OMP header and message summary', () => { assert.equal(summary.lastTs, Date.parse('2026-05-24T10:00:04.000Z')); }); +test('parsePiSessionFile reads title line before OMP session header', () => { + const dir = tmpDir(); + const file = path.join(dir, 'sessions', '--tmp--project--', '2026-05-24T10-00-00.000Z_pi-session-title.jsonl'); + writeJsonl(file, [ + { type: 'title', title: 'brief-improve' }, + { type: 'session', version: 3, id: 'pi-session-title', timestamp: '2026-05-24T10:00:00.000Z', cwd: '/tmp/project' }, + { type: 'message', timestamp: '2026-05-24T10:00:02.000Z', message: { role: 'user', content: [{ type: 'text', text: 'Please fix this' }] } }, + ]); + + const summary = parsePiSessionFile(file); + assert.equal(summary.sessionId, 'pi-session-title'); + assert.equal(summary.title, 'brief-improve'); +}); + test('parsePiSessionFile rejects unsafe header ids', () => { const dir = tmpDir(); const file = path.join(dir, 'sessions', '--tmp--project--', 'bad.jsonl');