From 457dc13ef9724bdbc846685d597ec3b7207c7ad5 Mon Sep 17 00:00:00 2001 From: iliya Date: Sun, 15 Mar 2026 22:04:39 +0200 Subject: [PATCH] feat: enhance TeamMemberLogsFinder and MemberLogsTab for improved preview handling - Added support for tracking and displaying recent thinking previews alongside output previews in TeamMemberLogsFinder. - Updated the return structure of log summaries to include last thinking previews and recent previews for better context. - Enhanced the MemberLogsTab component to utilize recent previews for lead users, improving the clarity and relevance of displayed messages. - Implemented filtering logic for task-scoped recent previews, ensuring only relevant messages are shown based on task intervals. --- .../services/team/TeamMemberLogsFinder.ts | 49 +++++++++- .../components/team/members/MemberLogsTab.tsx | 91 ++++++++++++++----- src/shared/types/team.ts | 4 + 3 files changed, 118 insertions(+), 26 deletions(-) diff --git a/src/main/services/team/TeamMemberLogsFinder.ts b/src/main/services/team/TeamMemberLogsFinder.ts index 53252b55..2b5bbc0d 100644 --- a/src/main/services/team/TeamMemberLogsFinder.ts +++ b/src/main/services/team/TeamMemberLogsFinder.ts @@ -59,6 +59,9 @@ interface StreamedMetadata { lastTimestamp: string | null; messageCount: number; lastOutputPreview: string | null; + lastThinkingPreview: string | null; + /** Recent thinking/output previews with timestamps for task-scoped filtering. */ + recentPreviews: { text: string; timestamp: string; kind: 'thinking' | 'output' }[]; } /** Result of attributing a subagent file to a team member. */ @@ -1178,6 +1181,8 @@ export class TeamMemberLogsFinder { isOngoing, filePath, lastOutputPreview: metadata.lastOutputPreview ?? undefined, + lastThinkingPreview: metadata.lastThinkingPreview ?? undefined, + recentPreviews: metadata.recentPreviews.length > 0 ? metadata.recentPreviews : undefined, }; } @@ -1425,6 +1430,8 @@ export class TeamMemberLogsFinder { isOngoing, filePath: jsonlPath, lastOutputPreview: metadata.lastOutputPreview ?? undefined, + lastThinkingPreview: metadata.lastThinkingPreview ?? undefined, + recentPreviews: metadata.recentPreviews.length > 0 ? metadata.recentPreviews : undefined, }; } @@ -1437,6 +1444,9 @@ export class TeamMemberLogsFinder { let lastTimestamp: string | null = null; let messageCount = 0; let lastOutputPreview: string | null = null; + let lastThinkingPreview: string | null = null; + const MAX_RECENT_PREVIEWS = 20; + const recentPreviews: StreamedMetadata['recentPreviews'] = []; try { const stream = createReadStream(filePath, { encoding: 'utf8' }); @@ -1458,7 +1468,25 @@ export class TeamMemberLogsFinder { // Track last assistant text output (cheap regex, overwrites on each match). if (trimmed.includes('"role":"assistant"') || trimmed.includes('"role": "assistant"')) { const preview = TeamMemberLogsFinder.extractAssistantPreview(trimmed); - if (preview) lastOutputPreview = preview; + if (preview) { + lastOutputPreview = preview; + if (ts) { + recentPreviews.push({ text: preview, timestamp: ts, kind: 'output' }); + if (recentPreviews.length > MAX_RECENT_PREVIEWS) recentPreviews.shift(); + } + } + } + + // Track last thinking block (cheap regex). + if (trimmed.includes('"type":"thinking"') || trimmed.includes('"type": "thinking"')) { + const thinkingPreview = TeamMemberLogsFinder.extractThinkingPreview(trimmed); + if (thinkingPreview) { + lastThinkingPreview = thinkingPreview; + if (ts) { + recentPreviews.push({ text: thinkingPreview, timestamp: ts, kind: 'thinking' }); + if (recentPreviews.length > MAX_RECENT_PREVIEWS) recentPreviews.shift(); + } + } } } rl.close(); @@ -1467,7 +1495,7 @@ export class TeamMemberLogsFinder { // ignore — return whatever we collected so far } - return { firstTimestamp, lastTimestamp, messageCount, lastOutputPreview }; + return { firstTimestamp, lastTimestamp, messageCount, lastOutputPreview, lastThinkingPreview, recentPreviews }; } private extractTimestampFromLine(line: string): string | null { @@ -1503,6 +1531,23 @@ export class TeamMemberLogsFinder { return null; } + /** + * Extract a short preview from a thinking block line via regex. + * Thinking blocks use {"type":"thinking","thinking":"..."}. + */ + private static extractThinkingPreview(line: string): string | null { + const match = /"thinking"\s*:\s*"([^"]{1,200})/.exec(line); + if (match?.[1]) { + const raw = match[1] + .replace(/\\n/g, ' ') + .replace(/\\t/g, ' ') + .replace(/\s+/g, ' ') + .trim(); + return raw.length > 120 ? raw.slice(0, 120) + '...' : raw; + } + return null; + } + private async probeFirstTimestamp( filePath: string, maxLines = ATTRIBUTION_SCAN_LINES diff --git a/src/renderer/components/team/members/MemberLogsTab.tsx b/src/renderer/components/team/members/MemberLogsTab.tsx index 41eb74a9..8be2f609 100644 --- a/src/renderer/components/team/members/MemberLogsTab.tsx +++ b/src/renderer/components/team/members/MemberLogsTab.tsx @@ -266,43 +266,86 @@ export const MemberLogsTab = ({ }, [shouldShowPreview, showLeadPreview, showSubagentPreview, sortedLogs, taskOwner]); const allPreviewMessages = useMemo((): SubagentPreviewMessage[] => { + // Build lead messages from recentPreviews, filtered by taskWorkIntervals. + const buildLeadPreviewMessages = (): SubagentPreviewMessage[] => { + if (!previewLog) return []; + + // Use task-scoped recentPreviews when available + if (previewLog.recentPreviews && previewLog.recentPreviews.length > 0 && taskWorkIntervals && taskWorkIntervals.length > 0) { + const GRACE_BEFORE = 30_000; + const GRACE_AFTER = 15_000; + const now = Date.now(); + const intervals = taskWorkIntervals + .map((i) => { + const s = Date.parse(i.startedAt); + if (!Number.isFinite(s)) return null; + const e = typeof i.completedAt === 'string' ? Date.parse(i.completedAt) : null; + return { startMs: s - GRACE_BEFORE, endMs: e != null && Number.isFinite(e) ? e + GRACE_AFTER : now + GRACE_AFTER }; + }) + .filter((v): v is { startMs: number; endMs: number } => v !== null); + + if (intervals.length > 0) { + const scoped = previewLog.recentPreviews.filter((p) => { + const ms = Date.parse(p.timestamp); + if (!Number.isFinite(ms)) return false; + return intervals.some((i) => ms >= i.startMs && ms <= i.endMs); + }); + if (scoped.length > 0) { + return scoped + .reverse() + .map((p, idx) => ({ + id: `${previewLog.sessionId}:recent:${idx}`, + timestamp: new Date(p.timestamp), + kind: 'output' as const, + label: p.kind === 'thinking' ? 'Thinking' : 'Output', + content: p.text, + })); + } + } + } + + // Fallback to last output/thinking + const msgs: SubagentPreviewMessage[] = []; + if (previewLog.lastOutputPreview) { + msgs.push({ + id: `${previewLog.sessionId}:lastOutput`, + timestamp: new Date(previewLog.startTime), + kind: 'output', + label: 'Output', + content: previewLog.lastOutputPreview, + }); + } + if (previewLog.lastThinkingPreview) { + msgs.push({ + id: `${previewLog.sessionId}:lastThinking`, + timestamp: new Date(previewLog.startTime), + kind: 'output', + label: 'Thinking', + content: previewLog.lastThinkingPreview, + }); + } + return msgs; + }; + if (!previewChunks || previewChunks.length === 0) { - // For lead preview without chunks, fall back to lastOutputPreview from the log summary. - if (showLeadPreview && previewLog?.lastOutputPreview) { - return [ - { - id: `${previewLog.sessionId}:lastOutput`, - timestamp: new Date(previewLog.startTime), - kind: 'output', - label: 'Output', - content: previewLog.lastOutputPreview, - }, - ]; + if (showLeadPreview) { + return buildLeadPreviewMessages(); } return []; } const raw = extractSubagentPreviewMessages(previewChunks); // For lead preview, user messages are system-generated prompts (not useful). // Show only AI outputs — the actual work results. - // If no outputs found, fall back to lastOutputPreview from the log summary. + // If no outputs found, fall back to summary previews. if (showLeadPreview) { const outputs = raw.filter((m) => m.kind !== 'user'); if (outputs.length > 0) return outputs; - if (previewLog?.lastOutputPreview) { - return [ - { - id: `${previewLog.sessionId}:lastOutput`, - timestamp: new Date(previewLog.startTime), - kind: 'output', - label: 'Output', - content: previewLog.lastOutputPreview, - }, - ]; - } + const fallback = buildLeadPreviewMessages(); + if (fallback.length > 0) return fallback; return raw; // ultimate fallback: show everything including user messages } return raw; - }, [previewChunks, showLeadPreview, previewLog]); + }, [previewChunks, showLeadPreview, previewLog, taskWorkIntervals]); const previewMessages = useMemo((): SubagentPreviewMessage[] => { return allPreviewMessages.slice(0, previewVisibleCount); diff --git a/src/shared/types/team.ts b/src/shared/types/team.ts index 45b0f12b..3168770f 100644 --- a/src/shared/types/team.ts +++ b/src/shared/types/team.ts @@ -618,6 +618,10 @@ export interface MemberLogSummaryBase { filePath?: string; /** Short preview of the last assistant output (truncated). */ lastOutputPreview?: string; + /** Short preview of the last thinking block (truncated). */ + lastThinkingPreview?: string; + /** Recent thinking/output previews with timestamps for task-scoped filtering. */ + recentPreviews?: { text: string; timestamp: string; kind: 'thinking' | 'output' }[]; } export interface MemberSubagentLogSummary extends MemberLogSummaryBase {