feat(bf-52d6): integrate conversation transcript parser with store and web API
Integrate the conversationParser module with the InMemoryEventStore and web server to provide complete conversation transcript functionality. Store integration: - Add conversation session caching with 5-second TTL - Invalidate cache when conversation events are added - Add methods: getConversationSessions, getWorkerConversationSessions, getBeadConversationSession, getConversationSession, getConversationEvents, getWorkerConversationEvents, getBeadConversationEvents Web API integration: - GET /api/conversations/sessions - Get all conversation sessions - GET /api/conversations/workers/:workerId - Get sessions for worker - GET /api/conversations/beads/:beadId - Get session for bead - GET /api/conversations/:sessionId - Get session by ID - GET /api/conversations/events - Get all conversation events - GET /api/conversations/workers/:workerId/events - Get worker events - GET /api/conversations/beads/:beadId/events - Get bead events Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
3da90f1be0
commit
c627976dbc
2 changed files with 210 additions and 0 deletions
116
src/store.ts
116
src/store.ts
|
|
@ -44,6 +44,8 @@ import {
|
|||
TimelapseOptions,
|
||||
HeatmapTimelapse,
|
||||
HeatmapSnapshot,
|
||||
ConversationSession,
|
||||
ConversationEvent,
|
||||
compareEventsBySequence,
|
||||
} from './types.js';
|
||||
import { isWorkerStuck } from './tui/utils/stuckDetection.js';
|
||||
|
|
@ -55,6 +57,12 @@ import { WorkerAnalytics, getWorkerAnalytics } from './workerAnalytics.js';
|
|||
import { CostTracker } from './tui/utils/costTracking.js';
|
||||
import { SemanticNarrativeGenerator, getSemanticNarrativeManager } from './semanticNarrative.js';
|
||||
import { HistoricalStore, getHistoricalStore } from './historicalStore.js';
|
||||
import {
|
||||
buildConversationSessions,
|
||||
getWorkerConversationSessions,
|
||||
getBeadConversationSession,
|
||||
extractConversationEvents,
|
||||
} from './conversationParser.js';
|
||||
|
||||
/** Time window (in ms) to consider events as concurrent */
|
||||
const COLLISION_WINDOW_MS = 5000;
|
||||
|
|
@ -157,6 +165,20 @@ export class InMemoryEventStore implements EventStore {
|
|||
add(event: LogEvent): void {
|
||||
this.events.push(event);
|
||||
|
||||
// Invalidate conversation cache when conversation events are added
|
||||
const spanName = String(event.span_name || '');
|
||||
if (
|
||||
spanName.includes('llm.') ||
|
||||
spanName.includes('tool.') ||
|
||||
event.prompt ||
|
||||
event.response ||
|
||||
event.tool ||
|
||||
event.msg.includes('llm.request') ||
|
||||
event.msg.includes('tool.call')
|
||||
) {
|
||||
this.invalidateConversationCache();
|
||||
}
|
||||
|
||||
// Populate secondary index keyed on (worker, sequence)
|
||||
if (event.sequence != null && event.sequence >= 0) {
|
||||
this.sequenceIndex.set(`${event.worker}:${event.sequence}`, event);
|
||||
|
|
@ -312,6 +334,9 @@ export class InMemoryEventStore implements EventStore {
|
|||
clearTimeout(this.batchTimeout);
|
||||
this.batchTimeout = null;
|
||||
}
|
||||
|
||||
// Clear conversation cache
|
||||
this.invalidateConversationCache();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1938,6 +1963,97 @@ export class InMemoryEventStore implements EventStore {
|
|||
getSemanticNarrativeManager(): SemanticNarrativeGenerator {
|
||||
return this.semanticNarrativeManager;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Conversation Transcript Methods
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Cached conversation sessions built from events
|
||||
*/
|
||||
private conversationSessions: ConversationSession[] | null = null;
|
||||
private conversationSessionsCacheTime: number = 0;
|
||||
private readonly CONVERSATION_CACHE_TTL_MS = 5000; // 5 seconds
|
||||
|
||||
/**
|
||||
* Invalidate the conversation sessions cache
|
||||
*/
|
||||
private invalidateConversationCache(): void {
|
||||
this.conversationSessions = null;
|
||||
this.conversationSessionsCacheTime = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build or retrieve cached conversation sessions
|
||||
*/
|
||||
private buildConversationSessions(): ConversationSession[] {
|
||||
const now = Date.now();
|
||||
|
||||
// Return cached sessions if still valid
|
||||
if (
|
||||
this.conversationSessions &&
|
||||
(now - this.conversationSessionsCacheTime) < this.CONVERSATION_CACHE_TTL_MS
|
||||
) {
|
||||
return this.conversationSessions;
|
||||
}
|
||||
|
||||
// Build new sessions from events
|
||||
this.conversationSessions = buildConversationSessions(this.events);
|
||||
this.conversationSessionsCacheTime = now;
|
||||
return this.conversationSessions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all conversation sessions
|
||||
*/
|
||||
getConversationSessions(): ConversationSession[] {
|
||||
return this.buildConversationSessions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation sessions for a specific worker
|
||||
*/
|
||||
getWorkerConversationSessions(workerId: string): ConversationSession[] {
|
||||
return getWorkerConversationSessions(this.events, workerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation session for a specific bead
|
||||
*/
|
||||
getBeadConversationSession(beadId: string): ConversationSession | null {
|
||||
return getBeadConversationSession(this.events, beadId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific conversation session by ID
|
||||
*/
|
||||
getConversationSession(sessionId: string): ConversationSession | undefined {
|
||||
const sessions = this.buildConversationSessions();
|
||||
return sessions.find(s => s.id === sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all conversation events across all sessions
|
||||
*/
|
||||
getConversationEvents(): ConversationEvent[] {
|
||||
return extractConversationEvents(this.events);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation events for a specific worker
|
||||
*/
|
||||
getWorkerConversationEvents(workerId: string): ConversationEvent[] {
|
||||
const workerEvents = this.events.filter(e => e.worker === workerId);
|
||||
return extractConversationEvents(workerEvents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get conversation events for a specific bead
|
||||
*/
|
||||
getBeadConversationEvents(beadId: string): ConversationEvent[] {
|
||||
const beadEvents = this.events.filter(e => e.bead === beadId);
|
||||
return extractConversationEvents(beadEvents);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1166,6 +1166,100 @@ export function createWebServer(options: WebServerOptions): WebServer {
|
|||
}
|
||||
});
|
||||
|
||||
// ============================================
|
||||
// Conversation Transcript API
|
||||
// ============================================
|
||||
|
||||
// Get all conversation sessions
|
||||
app.get('/api/conversations/sessions', (_req: Request, res: Response) => {
|
||||
try {
|
||||
const sessions = store.getConversationSessions();
|
||||
res.json(sessions);
|
||||
} catch (err) {
|
||||
console.error('Error getting conversation sessions:', err);
|
||||
res.status(500).json({ error: 'Failed to get conversation sessions' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get conversation sessions for a specific worker
|
||||
app.get('/api/conversations/workers/:workerId', (req: Request, res: Response) => {
|
||||
try {
|
||||
const workerId = req.params.workerId as string;
|
||||
const sessions = store.getWorkerConversationSessions(workerId);
|
||||
res.json(sessions);
|
||||
} catch (err) {
|
||||
console.error('Error getting worker conversation sessions:', err);
|
||||
res.status(500).json({ error: 'Failed to get worker conversation sessions' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get conversation session for a specific bead
|
||||
app.get('/api/conversations/beads/:beadId', (req: Request, res: Response) => {
|
||||
try {
|
||||
const beadId = req.params.beadId as string;
|
||||
const session = store.getBeadConversationSession(beadId);
|
||||
if (!session) {
|
||||
res.status(404).json({ error: 'Conversation session not found' });
|
||||
return;
|
||||
}
|
||||
res.json(session);
|
||||
} catch (err) {
|
||||
console.error('Error getting bead conversation session:', err);
|
||||
res.status(500).json({ error: 'Failed to get bead conversation session' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get a specific conversation session by ID
|
||||
app.get('/api/conversations/:sessionId', (req: Request, res: Response) => {
|
||||
try {
|
||||
const sessionId = req.params.sessionId as string;
|
||||
const session = store.getConversationSession(sessionId);
|
||||
if (!session) {
|
||||
res.status(404).json({ error: 'Conversation session not found' });
|
||||
return;
|
||||
}
|
||||
res.json(session);
|
||||
} catch (err) {
|
||||
console.error('Error getting conversation session:', err);
|
||||
res.status(500).json({ error: 'Failed to get conversation session' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get all conversation events across all sessions
|
||||
app.get('/api/conversations/events', (_req: Request, res: Response) => {
|
||||
try {
|
||||
const events = store.getConversationEvents();
|
||||
res.json(events);
|
||||
} catch (err) {
|
||||
console.error('Error getting conversation events:', err);
|
||||
res.status(500).json({ error: 'Failed to get conversation events' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get conversation events for a specific worker
|
||||
app.get('/api/conversations/workers/:workerId/events', (req: Request, res: Response) => {
|
||||
try {
|
||||
const workerId = req.params.workerId as string;
|
||||
const events = store.getWorkerConversationEvents(workerId);
|
||||
res.json(events);
|
||||
} catch (err) {
|
||||
console.error('Error getting worker conversation events:', err);
|
||||
res.status(500).json({ error: 'Failed to get worker conversation events' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get conversation events for a specific bead
|
||||
app.get('/api/conversations/beads/:beadId/events', (req: Request, res: Response) => {
|
||||
try {
|
||||
const beadId = req.params.beadId as string;
|
||||
const events = store.getBeadConversationEvents(beadId);
|
||||
res.json(events);
|
||||
} catch (err) {
|
||||
console.error('Error getting bead conversation events:', err);
|
||||
res.status(500).json({ error: 'Failed to get bead conversation events' });
|
||||
}
|
||||
});
|
||||
|
||||
// Serve static frontend files
|
||||
const staticPath = join(__dirname, 'public');
|
||||
app.use(express.static(staticPath));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue