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;
|
lastTimestamp: string | null;
|
||||||
messageCount: number;
|
messageCount: number;
|
||||||
lastOutputPreview: string | null;
|
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. */
|
/** Result of attributing a subagent file to a team member. */
|
||||||
|
|
@ -1178,6 +1181,8 @@ export class TeamMemberLogsFinder {
|
||||||
isOngoing,
|
isOngoing,
|
||||||
filePath,
|
filePath,
|
||||||
lastOutputPreview: metadata.lastOutputPreview ?? undefined,
|
lastOutputPreview: metadata.lastOutputPreview ?? undefined,
|
||||||
|
lastThinkingPreview: metadata.lastThinkingPreview ?? undefined,
|
||||||
|
recentPreviews: metadata.recentPreviews.length > 0 ? metadata.recentPreviews : undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1425,6 +1430,8 @@ export class TeamMemberLogsFinder {
|
||||||
isOngoing,
|
isOngoing,
|
||||||
filePath: jsonlPath,
|
filePath: jsonlPath,
|
||||||
lastOutputPreview: metadata.lastOutputPreview ?? undefined,
|
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 lastTimestamp: string | null = null;
|
||||||
let messageCount = 0;
|
let messageCount = 0;
|
||||||
let lastOutputPreview: string | null = null;
|
let lastOutputPreview: string | null = null;
|
||||||
|
let lastThinkingPreview: string | null = null;
|
||||||
|
const MAX_RECENT_PREVIEWS = 20;
|
||||||
|
const recentPreviews: StreamedMetadata['recentPreviews'] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const stream = createReadStream(filePath, { encoding: 'utf8' });
|
const stream = createReadStream(filePath, { encoding: 'utf8' });
|
||||||
|
|
@ -1458,7 +1468,25 @@ export class TeamMemberLogsFinder {
|
||||||
// Track last assistant text output (cheap regex, overwrites on each match).
|
// Track last assistant text output (cheap regex, overwrites on each match).
|
||||||
if (trimmed.includes('"role":"assistant"') || trimmed.includes('"role": "assistant"')) {
|
if (trimmed.includes('"role":"assistant"') || trimmed.includes('"role": "assistant"')) {
|
||||||
const preview = TeamMemberLogsFinder.extractAssistantPreview(trimmed);
|
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();
|
rl.close();
|
||||||
|
|
@ -1467,7 +1495,7 @@ export class TeamMemberLogsFinder {
|
||||||
// ignore — return whatever we collected so far
|
// 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 {
|
private extractTimestampFromLine(line: string): string | null {
|
||||||
|
|
@ -1503,6 +1531,23 @@ export class TeamMemberLogsFinder {
|
||||||
return null;
|
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(
|
private async probeFirstTimestamp(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
maxLines = ATTRIBUTION_SCAN_LINES
|
maxLines = ATTRIBUTION_SCAN_LINES
|
||||||
|
|
|
||||||
|
|
@ -266,43 +266,86 @@ export const MemberLogsTab = ({
|
||||||
}, [shouldShowPreview, showLeadPreview, showSubagentPreview, sortedLogs, taskOwner]);
|
}, [shouldShowPreview, showLeadPreview, showSubagentPreview, sortedLogs, taskOwner]);
|
||||||
|
|
||||||
const allPreviewMessages = useMemo((): SubagentPreviewMessage[] => {
|
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) {
|
if (!previewChunks || previewChunks.length === 0) {
|
||||||
// For lead preview without chunks, fall back to lastOutputPreview from the log summary.
|
if (showLeadPreview) {
|
||||||
if (showLeadPreview && previewLog?.lastOutputPreview) {
|
return buildLeadPreviewMessages();
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: `${previewLog.sessionId}:lastOutput`,
|
|
||||||
timestamp: new Date(previewLog.startTime),
|
|
||||||
kind: 'output',
|
|
||||||
label: 'Output',
|
|
||||||
content: previewLog.lastOutputPreview,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const raw = extractSubagentPreviewMessages(previewChunks);
|
const raw = extractSubagentPreviewMessages(previewChunks);
|
||||||
// For lead preview, user messages are system-generated prompts (not useful).
|
// For lead preview, user messages are system-generated prompts (not useful).
|
||||||
// Show only AI outputs — the actual work results.
|
// 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) {
|
if (showLeadPreview) {
|
||||||
const outputs = raw.filter((m) => m.kind !== 'user');
|
const outputs = raw.filter((m) => m.kind !== 'user');
|
||||||
if (outputs.length > 0) return outputs;
|
if (outputs.length > 0) return outputs;
|
||||||
if (previewLog?.lastOutputPreview) {
|
const fallback = buildLeadPreviewMessages();
|
||||||
return [
|
if (fallback.length > 0) return fallback;
|
||||||
{
|
|
||||||
id: `${previewLog.sessionId}:lastOutput`,
|
|
||||||
timestamp: new Date(previewLog.startTime),
|
|
||||||
kind: 'output',
|
|
||||||
label: 'Output',
|
|
||||||
content: previewLog.lastOutputPreview,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
return raw; // ultimate fallback: show everything including user messages
|
return raw; // ultimate fallback: show everything including user messages
|
||||||
}
|
}
|
||||||
return raw;
|
return raw;
|
||||||
}, [previewChunks, showLeadPreview, previewLog]);
|
}, [previewChunks, showLeadPreview, previewLog, taskWorkIntervals]);
|
||||||
|
|
||||||
const previewMessages = useMemo((): SubagentPreviewMessage[] => {
|
const previewMessages = useMemo((): SubagentPreviewMessage[] => {
|
||||||
return allPreviewMessages.slice(0, previewVisibleCount);
|
return allPreviewMessages.slice(0, previewVisibleCount);
|
||||||
|
|
|
||||||
|
|
@ -618,6 +618,10 @@ export interface MemberLogSummaryBase {
|
||||||
filePath?: string;
|
filePath?: string;
|
||||||
/** Short preview of the last assistant output (truncated). */
|
/** Short preview of the last assistant output (truncated). */
|
||||||
lastOutputPreview?: string;
|
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 {
|
export interface MemberSubagentLogSummary extends MemberLogSummaryBase {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue