UNPKG

@hivetechs/hive-ai

Version:

Real-time streaming AI consensus platform with HTTP+SSE MCP integration for Claude Code, VS Code, Cursor, and Windsurf - powered by OpenRouter's unified API

228 lines 7.14 kB
/** * Activity Logger for Dashboard Real-Time Tracking * * Provides event logging for all consensus pipeline activities, * enabling real-time dashboards and analytics. */ import { getDatabase } from './unified-database.js'; import { structuredLogger } from '../tools/structured-logger.js'; /** * Log an activity event to the database */ export async function logActivity(entry) { const logId = Math.random().toString(36).substr(2, 9); console.log(`[ACTIVITY-LOG-DEBUG] Attempting to log: ${entry.eventType} for conversation: ${entry.conversationId}`); try { const db = await getDatabase(); // Debug: log what we're trying to insert structuredLogger.debug('Attempting to log activity', { logId, eventType: entry.eventType, conversationId: entry.conversationId, stage: entry.stage, modelUsed: entry.modelUsed }); await db.run(` INSERT INTO activity_log ( event_type, conversation_id, user_id, stage, model_used, provider_name, cost, duration_ms, tokens_used, error_message, metadata ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ entry.eventType, entry.conversationId || null, entry.userId || null, entry.stage || null, entry.modelUsed || null, entry.providerName || null, entry.cost || null, entry.durationMs || null, entry.tokensUsed || null, entry.errorMessage || null, entry.metadata ? JSON.stringify(entry.metadata) : null ]); structuredLogger.debug('Activity logged successfully', { logId, eventType: entry.eventType, conversationId: entry.conversationId, stage: entry.stage }); } catch (error) { console.log(`[ACTIVITY-LOG-DEBUG] ERROR: ${error.message} for ${entry.eventType}`); // Don't throw - activity logging should not break the main flow structuredLogger.warn('Failed to log activity', { logId, eventType: entry.eventType, conversationId: entry.conversationId, stage: entry.stage, error: error.message, errorType: error.constructor.name, sqliteCode: error.code, stackTrace: error.stack?.split('\n')[0] }); } } /** * Get recent activity for dashboard display */ export async function getRecentActivity(limit = 10) { try { const db = await getDatabase(); const activities = await db.all(` SELECT datetime(created_at, 'localtime') as time, event_type, stage, model_used, cost, duration_ms, error_message, metadata FROM activity_log WHERE created_at > datetime('now', '-1 hour') ORDER BY created_at DESC LIMIT ? `, [limit]); return activities.map(activity => ({ ...activity, metadata: activity.metadata ? JSON.parse(activity.metadata) : null })); } catch (error) { structuredLogger.error('Failed to get recent activity', {}, error); return []; } } /** * Get count of active queries (started but not completed) */ export async function getActiveQueryCount() { try { const db = await getDatabase(); const result = await db.get(` SELECT COUNT(DISTINCT conversation_id) as active_count FROM activity_log WHERE event_type = 'query_start' AND conversation_id NOT IN ( SELECT conversation_id FROM activity_log WHERE event_type IN ('query_complete', 'query_error') AND conversation_id IS NOT NULL ) AND created_at > datetime('now', '-5 minutes') `); return result?.active_count || 0; } catch (error) { structuredLogger.error('Failed to get active query count', {}, error); return 0; } } /** * Get throughput (queries completed per hour) */ export async function getThroughput(hours = 1) { try { const db = await getDatabase(); const result = await db.get(` SELECT COUNT(DISTINCT conversation_id) as throughput FROM activity_log WHERE event_type = 'query_complete' AND created_at > datetime('now', '-${hours} hour') AND conversation_id IS NOT NULL `); return result?.throughput || 0; } catch (error) { structuredLogger.error('Failed to get throughput', {}, error); return 0; } } /** * Get model performance stats for leaderboard */ export async function getModelPerformanceStats(hours = 24) { try { const db = await getDatabase(); const stats = await db.all(` SELECT al.model_used, al.provider_name, COUNT(DISTINCT al.conversation_id) as usage_count, AVG(al.cost) as avg_cost, AVG(al.duration_ms) as avg_duration, SUM(CASE WHEN al.error_message IS NULL THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as success_rate, AVG(al.tokens_used) as avg_tokens FROM activity_log al WHERE al.event_type IN ('stage_complete', 'stage_error') AND al.created_at > datetime('now', '-${hours} hour') AND al.model_used IS NOT NULL GROUP BY al.model_used, al.provider_name ORDER BY success_rate DESC, avg_duration ASC LIMIT 10 `); return stats; } catch (error) { structuredLogger.error('Failed to get model performance stats', {}, error); return []; } } /** * Log query start event */ export async function logQueryStart(conversationId, userId, metadata) { await logActivity({ eventType: 'query_start', conversationId, userId, metadata: { ...metadata, source: 'logQueryStart' } }); } /** * Log query completion event */ export async function logQueryComplete(conversationId, totalCost, durationMs, tokensUsed, userId, metadata) { await logActivity({ eventType: 'query_complete', conversationId, userId, cost: totalCost, durationMs, tokensUsed, metadata }); } /** * Log stage event */ export async function logStageEvent(eventType, conversationId, stage, model, provider, userId, options) { await logActivity({ eventType, conversationId, userId, stage, modelUsed: model, providerName: provider, ...options }); } /** * Clean up old activity logs (retention policy) */ export async function cleanupOldActivities(daysToKeep = 30) { try { const db = await getDatabase(); const result = await db.run(` DELETE FROM activity_log WHERE created_at < datetime('now', '-${daysToKeep} days') `); structuredLogger.info('Cleaned up old activity logs', { deletedCount: result.changes }); } catch (error) { structuredLogger.error('Failed to cleanup old activities', {}, error); } } //# sourceMappingURL=activity-logger.js.map