From 5887187f2578af3f2f56792307810dea75822623 Mon Sep 17 00:00:00 2001 From: reviewer Date: Mon, 20 Jul 2026 22:01:06 +0200 Subject: [PATCH] sessions: thread captured titles onto SessionSummary and the report Same threading as prLinks: the cache-level title (last ai-title entry) rides onto SessionSummary in both the Claude and generic provider pipelines, and the sessions table gains a TITLE column (truncated at 38 chars, empty when a transcript never produced one, as subagent transcripts typically do not). JSON rows carry it verbatim. Groundwork for titles in the context browser, desktop Sessions view, and payload. --- src/parser.ts | 8 ++++++-- src/sessions-report.ts | 6 +++++- src/types.ts | 3 +++ tests/sessions-report.test.ts | 1 + 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/parser.ts b/src/parser.ts index a98e528d..7c911341 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -2047,6 +2047,7 @@ async function scanProjectDirs( const session = buildSessionSummary(sessionId, projectName, classifiedTurns, mcpInv, source) session.agentType = cachedFile.agentType if (cachedFile.prLinks?.length) session.prLinks = [...new Set(cachedFile.prLinks)].sort() + if (cachedFile.title) session.title = cachedFile.title if (session.apiCalls > 0) { const projectKey = cachedFile.canonicalCwd @@ -2744,7 +2745,7 @@ async function parseProviderSources( // Query-time: derive SessionSummary from all cached turns. // Uses seenKeys (shared across providers) for cross-provider dedup. - const sessionMap = new Map }>() + const sessionMap = new Map; title?: string }>() for (const source of servedSources) { const cachedFile = section.files[source.path] @@ -2777,12 +2778,14 @@ async function parseProviderSources( const links = (existing.prLinks ??= new Set()) for (const link of cachedFile.prLinks) links.add(link) } + if (!existing.title && cachedFile.title) existing.title = cachedFile.title } else { sessionMap.set(key, { project, projectPath: turn.calls[0]?.projectPath, turns: [classified], ...(cachedFile.prLinks?.length ? { prLinks: new Set(cachedFile.prLinks) } : {}), + ...(cachedFile.title ? { title: cachedFile.title } : {}), }) } } @@ -2826,10 +2829,11 @@ async function parseProviderSources( } const projectMap = new Map() - for (const [key, { project, projectPath, turns, prLinks }] of sessionMap) { + for (const [key, { project, projectPath, turns, prLinks, title }] of sessionMap) { const sessionId = key.split(':')[1] ?? key const session = buildSessionSummary(sessionId, project, turns) if (prLinks?.size) session.prLinks = [...prLinks].sort() + if (title) session.title = title if (session.apiCalls > 0) { const existing = projectMap.get(project) if (existing) { diff --git a/src/sessions-report.ts b/src/sessions-report.ts index 4d70c2a9..27c6752f 100644 --- a/src/sessions-report.ts +++ b/src/sessions-report.ts @@ -2,6 +2,8 @@ import type { ProjectSummary, SessionSummary } from './types.js' export type SessionRow = { sessionId: string + /// Captured human title, empty when the transcript never produced one. + title: string project: string provider: string models: string[] @@ -41,6 +43,7 @@ function durationMs(startedAt: string, endedAt: string): number { export function aggregateSessions(projects: ProjectSummary[]): SessionRow[] { return projects.flatMap(project => project.sessions.map(session => ({ sessionId: session.sessionId, + title: session.title ?? '', project: session.project || project.project, provider: inferProvider(session), models: Object.keys(session.modelBreakdown), @@ -63,9 +66,10 @@ export function renderJson(rows: SessionRow[]): string { } export function renderTable(rows: SessionRow[]): string { - const headers = ['SESSION', 'PROJECT', 'PROVIDER', 'MODELS', 'COST', 'SAVED', 'CALLS', 'TURNS', 'STARTED'] + const headers = ['SESSION', 'TITLE', 'PROJECT', 'PROVIDER', 'MODELS', 'COST', 'SAVED', 'CALLS', 'TURNS', 'STARTED'] const values = rows.map(row => [ row.sessionId, + row.title.length > 38 ? row.title.slice(0, 37) + '\u2026' : row.title, row.project, row.provider, row.models.join(', '), diff --git a/src/types.ts b/src/types.ts index 8b2686b2..aaefc866 100644 --- a/src/types.ts +++ b/src/types.ts @@ -202,6 +202,9 @@ export type SessionSummary = { /// GitHub PR URLs captured from the session transcript (session-level, /// deduplicated). Absent when none were observed. prLinks?: string[] + /// Human session title captured from the transcript (last ai-title entry). + /// Absent when the transcript never produced one. + title?: string modelBreakdown: Record toolBreakdown: Record mcpBreakdown: Record diff --git a/tests/sessions-report.test.ts b/tests/sessions-report.test.ts index 0100a345..f413cea3 100644 --- a/tests/sessions-report.test.ts +++ b/tests/sessions-report.test.ts @@ -78,6 +78,7 @@ describe('sessions JSON emitter', () => { expect(parsed).toEqual([{ sessionId: 'session-1', + title: '', project: 'codeburn', provider: 'claude', models: ['claude-sonnet-4-5'],