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.
This commit is contained in:
parent
af54cea68d
commit
457dc13ef9
3 changed files with 118 additions and 26 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue