From b7f28b7c7c3b781666fe00d3a4737e0d1012a399 Mon Sep 17 00:00:00 2001 From: zhangzqx Date: Tue, 16 Jun 2026 16:36:16 +0800 Subject: [PATCH] feat(sessions): show sub-task badge for sessions with parent_session_id - Add parent_session_id to session-cache SQL query and CachedSession type - Add parentId to SearchResult interface and all search queries - Display orange 'sub-task' tag in Sessions list when session.parentId exists - Update remote-sessions.ts, ssh-remote.ts, and preload types for compatibility --- src/main/remote-sessions.ts | 3 +++ src/main/session-cache.ts | 7 ++++++- src/main/sessions.ts | 13 +++++++++++-- src/main/ssh-remote.ts | 1 + src/preload/index.d.ts | 2 ++ src/preload/index.ts | 2 ++ src/renderer/src/screens/Sessions/Sessions.tsx | 6 ++++++ 7 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/main/remote-sessions.ts b/src/main/remote-sessions.ts index bab244359..0c878ea2e 100644 --- a/src/main/remote-sessions.ts +++ b/src/main/remote-sessions.ts @@ -234,6 +234,7 @@ function normalizeCachedSession(row: RemoteRecord): CachedSession { source: summary.source, messageCount: summary.messageCount, model: summary.model, + parentId: null, }; } @@ -304,6 +305,7 @@ export async function remoteSearchSessions( messageCount: numberValue(row.message_count), model: stringValue(row.model), snippet: stringValue(row.snippet), + parentId: null, }; }); @@ -354,6 +356,7 @@ async function remoteSearchRecentSessionMessages( messageCount: session.messageCount, model: session.model, snippet: highlightTextMatch(match, query).slice(0, 500), + parentId: null, } satisfies SearchResult; } catch { return null; diff --git a/src/main/session-cache.ts b/src/main/session-cache.ts index d53fa8960..59a814d0e 100644 --- a/src/main/session-cache.ts +++ b/src/main/session-cache.ts @@ -32,6 +32,7 @@ export interface CachedSession { source: string; messageCount: number; model: string; + parentId: string | null; } interface CacheData { @@ -104,7 +105,7 @@ export function syncSessionCache(): CachedSession[] { // Fetch sessions newer than last sync, or all if first sync const rows = db .prepare( - `SELECT s.id, s.started_at, s.source, s.message_count, s.model, s.title + `SELECT s.id, s.started_at, s.source, s.message_count, s.model, s.title, s.parent_session_id FROM sessions s WHERE s.started_at > ? ORDER BY s.started_at DESC`, @@ -116,6 +117,7 @@ export function syncSessionCache(): CachedSession[] { message_count: number; model: string; title: string | null; + parent_session_id: string | null; }>; // Index existing sessions by id once so the per-row update below is @@ -132,12 +134,14 @@ export function syncSessionCache(): CachedSession[] { const existing = existingById.get(row.id); if (existing) { existing.messageCount = row.message_count; + existing.parentId = row.parent_session_id || null; if (row.model) existing.model = row.model; if (row.title) existing.title = row.title; continue; } let title = row.title || ""; + const parentId = row.parent_session_id || null; if (!title) { try { const msg = db @@ -162,6 +166,7 @@ export function syncSessionCache(): CachedSession[] { source: row.source, messageCount: row.message_count, model: row.model || "", + parentId, }); } diff --git a/src/main/sessions.ts b/src/main/sessions.ts index d75994395..42d1ef13f 100644 --- a/src/main/sessions.ts +++ b/src/main/sessions.ts @@ -180,6 +180,7 @@ export interface SearchResult { messageCount: number; model: string; snippet: string; + parentId: string | null; } export function dedupeSearchRowsBySession( @@ -315,7 +316,8 @@ export function searchSessions(query: string, limit = 20): SearchResult[] { s.started_at, s.source, s.message_count, - s.model + s.model, + s.parent_session_id FROM sessions s WHERE LOWER(COALESCE(s.title, '')) LIKE ? ESCAPE '\\' OR LOWER(s.id) LIKE ? ESCAPE '\\' @@ -333,6 +335,7 @@ export function searchSessions(query: string, limit = 20): SearchResult[] { source: string; message_count: number; model: string; + parent_session_id: string | null; }>; const titleMatches = titleRows.map((r) => ({ @@ -365,6 +368,7 @@ export function searchSessions(query: string, limit = 20): SearchResult[] { s.source, s.message_count, s.model, + s.parent_session_id, snippet(messages_fts, 0, '<<', '>>', '...', 40) as snippet FROM messages_fts JOIN messages m ON m.id = messages_fts.rowid @@ -380,6 +384,7 @@ export function searchSessions(query: string, limit = 20): SearchResult[] { source: string; message_count: number; model: string; + parent_session_id: string | null; snippet: string; }>) : []; @@ -394,7 +399,8 @@ export function searchSessions(query: string, limit = 20): SearchResult[] { s.started_at, s.source, s.message_count, - s.model + s.model, + s.parent_session_id FROM messages m JOIN sessions s ON s.id = m.session_id WHERE LOWER(COALESCE(m.content, '')) LIKE ? ESCAPE '\\' @@ -413,6 +419,7 @@ export function searchSessions(query: string, limit = 20): SearchResult[] { source: string; message_count: number; model: string; + parent_session_id: string | null; }>; const messageMatches = messageRows.map((r) => ({ @@ -422,6 +429,7 @@ export function searchSessions(query: string, limit = 20): SearchResult[] { source: r.source, message_count: r.message_count, model: r.model, + parent_session_id: r.parent_session_id, snippet: decodeSearchSnippet(r.content, r.message_id, trimmedQuery), })); @@ -436,6 +444,7 @@ export function searchSessions(query: string, limit = 20): SearchResult[] { source: r.source, messageCount: r.message_count, model: r.model || "", + parentId: r.parent_session_id || null, snippet: r.snippet || "", })); } catch { diff --git a/src/main/ssh-remote.ts b/src/main/ssh-remote.ts index 2e13b1282..980378b0d 100644 --- a/src/main/ssh-remote.ts +++ b/src/main/ssh-remote.ts @@ -2166,6 +2166,7 @@ export async function sshListCachedSessions( source: s.source, messageCount: s.messageCount, model: s.model, + parentId: null, })); } diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 0ef64e674..c4dc27a21 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -677,6 +677,7 @@ interface HermesAPI { source: string; messageCount: number; model: string; + parentId: string | null; }> >; syncSessionCache: () => Promise< @@ -687,6 +688,7 @@ interface HermesAPI { source: string; messageCount: number; model: string; + parentId: string | null; }> >; updateSessionTitle: (sessionId: string, title: string) => Promise; diff --git a/src/preload/index.ts b/src/preload/index.ts index 2b0a8e7c9..fe090a93f 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -853,6 +853,7 @@ const hermesAPI = { source: string; messageCount: number; model: string; + parentId: string | null; }> > => ipcRenderer.invoke("list-cached-sessions", limit, offset), @@ -864,6 +865,7 @@ const hermesAPI = { source: string; messageCount: number; model: string; + parentId: string | null; }> > => ipcRenderer.invoke("sync-session-cache"), diff --git a/src/renderer/src/screens/Sessions/Sessions.tsx b/src/renderer/src/screens/Sessions/Sessions.tsx index f5a3c85cd..3c1b1454c 100644 --- a/src/renderer/src/screens/Sessions/Sessions.tsx +++ b/src/renderer/src/screens/Sessions/Sessions.tsx @@ -9,6 +9,7 @@ interface CachedSession { source: string; messageCount: number; model: string; + parentId: string | null; } interface SearchResult { @@ -234,6 +235,11 @@ const SessionCard = memo(function SessionCard({ {session.source} + {session.parentId && ( + + sub-task + + )} {session.messageCount} msg{session.messageCount !== 1 ? "s" : ""}