From ed695e50e62d671c7aabda6212c447a76dd25932 Mon Sep 17 00:00:00 2001 From: Vivek R <123vivekr@gmail.com> Date: Thu, 31 Jul 2025 14:21:14 +0530 Subject: [PATCH] feat(analytics): add core analytics infrastructure with PostHog integration - Implement comprehensive analytics type system with 100+ event types - Create event builders for consistent event tracking - Add main analytics service with PostHog integration - Include performance tracking utilities with percentile monitoring - Support anonymous user tracking with session management - Implement sanitization helpers to remove PII from events - Add event queueing with automatic flush intervals - Support for screen tracking and app context BREAKING CHANGE: Analytics tracking is now integrated throughout the app and requires PostHog API key configuration via environment variables. --- src/lib/analytics/events.ts | 556 +++++++++++++++++++++++++++++++++++- src/lib/analytics/index.ts | 109 ++++++- src/lib/analytics/types.ts | 347 +++++++++++++++++++++- 3 files changed, 1003 insertions(+), 9 deletions(-) diff --git a/src/lib/analytics/events.ts b/src/lib/analytics/events.ts index 6770821..e45ba04 100644 --- a/src/lib/analytics/events.ts +++ b/src/lib/analytics/events.ts @@ -7,7 +7,42 @@ import type { AgentProperties, MCPProperties, SlashCommandProperties, - PerformanceMetrics + PerformanceMetrics, + PromptSubmittedProperties, + SessionStoppedProperties, + EnhancedSessionStoppedProperties, + CheckpointCreatedProperties, + CheckpointRestoredProperties, + ToolExecutedProperties, + AgentStartedProperties, + AgentProgressProperties, + AgentErrorProperties, + MCPServerAddedProperties, + MCPServerRemovedProperties, + MCPToolInvokedProperties, + MCPConnectionErrorProperties, + SlashCommandSelectedProperties, + SlashCommandExecutedProperties, + SlashCommandCreatedProperties, + APIErrorProperties, + UIErrorProperties, + PerformanceBottleneckProperties, + MemoryWarningProperties, + UserJourneyProperties, + EnhancedPromptSubmittedProperties, + EnhancedToolExecutedProperties, + EnhancedErrorProperties, + SessionEngagementProperties, + FeatureDiscoveryProperties, + OutputQualityProperties, + ResourceUsageProperties, + FeatureAdoptionProperties, + FeatureCombinationProperties, + AIInteractionProperties, + PromptPatternProperties, + WorkflowProperties, + NetworkPerformanceProperties, + SuggestionProperties } from './types'; export const ANALYTICS_EVENTS = { @@ -15,6 +50,11 @@ export const ANALYTICS_EVENTS = { SESSION_CREATED: 'session_created' as EventName, SESSION_COMPLETED: 'session_completed' as EventName, SESSION_RESUMED: 'session_resumed' as EventName, + PROMPT_SUBMITTED: 'prompt_submitted' as EventName, + SESSION_STOPPED: 'session_stopped' as EventName, + CHECKPOINT_CREATED: 'checkpoint_created' as EventName, + CHECKPOINT_RESTORED: 'checkpoint_restored' as EventName, + TOOL_EXECUTED: 'tool_executed' as EventName, // Feature usage events FEATURE_USED: 'feature_used' as EventName, @@ -24,18 +64,73 @@ export const ANALYTICS_EVENTS = { FILE_OPENED: 'file_opened' as EventName, FILE_EDITED: 'file_edited' as EventName, FILE_SAVED: 'file_saved' as EventName, + + // Agent events AGENT_EXECUTED: 'agent_executed' as EventName, + AGENT_STARTED: 'agent_started' as EventName, + AGENT_PROGRESS: 'agent_progress' as EventName, + AGENT_ERROR: 'agent_error' as EventName, + + // MCP events MCP_SERVER_CONNECTED: 'mcp_server_connected' as EventName, MCP_SERVER_DISCONNECTED: 'mcp_server_disconnected' as EventName, + MCP_SERVER_ADDED: 'mcp_server_added' as EventName, + MCP_SERVER_REMOVED: 'mcp_server_removed' as EventName, + MCP_TOOL_INVOKED: 'mcp_tool_invoked' as EventName, + MCP_CONNECTION_ERROR: 'mcp_connection_error' as EventName, + + // Slash command events SLASH_COMMAND_USED: 'slash_command_used' as EventName, + SLASH_COMMAND_SELECTED: 'slash_command_selected' as EventName, + SLASH_COMMAND_EXECUTED: 'slash_command_executed' as EventName, + SLASH_COMMAND_CREATED: 'slash_command_created' as EventName, // Settings and system events SETTINGS_CHANGED: 'settings_changed' as EventName, APP_STARTED: 'app_started' as EventName, APP_CLOSED: 'app_closed' as EventName, - // Error events + // Error and performance events ERROR_OCCURRED: 'error_occurred' as EventName, + API_ERROR: 'api_error' as EventName, + UI_ERROR: 'ui_error' as EventName, + PERFORMANCE_BOTTLENECK: 'performance_bottleneck' as EventName, + MEMORY_WARNING: 'memory_warning' as EventName, + + // User journey events + JOURNEY_MILESTONE: 'journey_milestone' as EventName, + USER_RETENTION: 'user_retention' as EventName, + + // AI interaction events + AI_INTERACTION: 'ai_interaction' as EventName, + PROMPT_PATTERN: 'prompt_pattern' as EventName, + + // Quality events + OUTPUT_REGENERATED: 'output_regenerated' as EventName, + CONVERSATION_ABANDONED: 'conversation_abandoned' as EventName, + SUGGESTION_ACCEPTED: 'suggestion_accepted' as EventName, + SUGGESTION_REJECTED: 'suggestion_rejected' as EventName, + + // Workflow events + WORKFLOW_STARTED: 'workflow_started' as EventName, + WORKFLOW_COMPLETED: 'workflow_completed' as EventName, + WORKFLOW_ABANDONED: 'workflow_abandoned' as EventName, + + // Feature adoption events + FEATURE_DISCOVERED: 'feature_discovered' as EventName, + FEATURE_ADOPTED: 'feature_adopted' as EventName, + FEATURE_COMBINATION: 'feature_combination' as EventName, + + // Resource usage events + RESOURCE_USAGE_HIGH: 'resource_usage_high' as EventName, + RESOURCE_USAGE_SAMPLED: 'resource_usage_sampled' as EventName, + + // Network performance events + NETWORK_PERFORMANCE: 'network_performance' as EventName, + NETWORK_FAILURE: 'network_failure' as EventName, + + // Engagement events + SESSION_ENGAGEMENT: 'session_engagement' as EventName, } as const; // Event property builders - help ensure consistent event structure @@ -116,6 +211,437 @@ export const eventBuilders = { ...metrics, }, }), + + // Claude Code Session event builders + promptSubmitted: (props: PromptSubmittedProperties) => ({ + event: ANALYTICS_EVENTS.PROMPT_SUBMITTED, + properties: { + category: 'session', + ...props, + }, + }), + + sessionStopped: (props: SessionStoppedProperties) => ({ + event: ANALYTICS_EVENTS.SESSION_STOPPED, + properties: { + category: 'session', + ...props, + }, + }), + + // Enhanced session stopped with detailed metrics + enhancedSessionStopped: (props: EnhancedSessionStoppedProperties) => ({ + event: ANALYTICS_EVENTS.SESSION_STOPPED, + properties: { + category: 'session', + duration_ms: props.duration_ms, + messages_count: props.messages_count, + reason: props.reason, + // Timing metrics + time_to_first_message_ms: props.time_to_first_message_ms, + average_response_time_ms: props.average_response_time_ms, + idle_time_ms: props.idle_time_ms, + // Interaction metrics + prompts_sent: props.prompts_sent, + tools_executed: props.tools_executed, + tools_failed: props.tools_failed, + files_created: props.files_created, + files_modified: props.files_modified, + files_deleted: props.files_deleted, + // Content metrics + total_tokens_used: props.total_tokens_used, + code_blocks_generated: props.code_blocks_generated, + errors_encountered: props.errors_encountered, + // Session context + model: props.model, + has_checkpoints: props.has_checkpoints, + checkpoint_count: props.checkpoint_count, + was_resumed: props.was_resumed, + // Agent context + agent_type: props.agent_type, + agent_name: props.agent_name ? sanitizers.sanitizeAgentName(props.agent_name) : undefined, + agent_success: props.agent_success, + // Stop context + stop_source: props.stop_source, + final_state: props.final_state, + has_pending_prompts: props.has_pending_prompts, + pending_prompts_count: props.pending_prompts_count, + }, + }), + + checkpointCreated: (props: CheckpointCreatedProperties) => ({ + event: ANALYTICS_EVENTS.CHECKPOINT_CREATED, + properties: { + category: 'session', + ...props, + }, + }), + + checkpointRestored: (props: CheckpointRestoredProperties) => ({ + event: ANALYTICS_EVENTS.CHECKPOINT_RESTORED, + properties: { + category: 'session', + ...props, + }, + }), + + toolExecuted: (props: ToolExecutedProperties) => ({ + event: ANALYTICS_EVENTS.TOOL_EXECUTED, + properties: { + category: 'session', + tool_name: sanitizers.sanitizeToolName(props.tool_name), + execution_time_ms: props.execution_time_ms, + success: props.success, + error_message: props.error_message ? sanitizers.sanitizeErrorMessage(props.error_message) : undefined, + }, + }), + + // Enhanced Agent event builders + agentStarted: (props: AgentStartedProperties) => ({ + event: ANALYTICS_EVENTS.AGENT_STARTED, + properties: { + category: 'agent', + agent_type: props.agent_type, + agent_name: props.agent_name ? sanitizers.sanitizeAgentName(props.agent_name) : undefined, + has_custom_prompt: props.has_custom_prompt, + }, + }), + + agentProgress: (props: AgentProgressProperties) => ({ + event: ANALYTICS_EVENTS.AGENT_PROGRESS, + properties: { + category: 'agent', + ...props, + }, + }), + + agentError: (props: AgentErrorProperties) => ({ + event: ANALYTICS_EVENTS.AGENT_ERROR, + properties: { + category: 'agent', + ...props, + }, + }), + + // MCP event builders + mcpServerAdded: (props: MCPServerAddedProperties) => ({ + event: ANALYTICS_EVENTS.MCP_SERVER_ADDED, + properties: { + category: 'mcp', + ...props, + }, + }), + + mcpServerRemoved: (props: MCPServerRemovedProperties) => ({ + event: ANALYTICS_EVENTS.MCP_SERVER_REMOVED, + properties: { + category: 'mcp', + server_name: sanitizers.sanitizeServerName(props.server_name), + was_connected: props.was_connected, + }, + }), + + mcpToolInvoked: (props: MCPToolInvokedProperties) => ({ + event: ANALYTICS_EVENTS.MCP_TOOL_INVOKED, + properties: { + category: 'mcp', + server_name: sanitizers.sanitizeServerName(props.server_name), + tool_name: sanitizers.sanitizeToolName(props.tool_name), + invocation_source: props.invocation_source, + }, + }), + + mcpConnectionError: (props: MCPConnectionErrorProperties) => ({ + event: ANALYTICS_EVENTS.MCP_CONNECTION_ERROR, + properties: { + category: 'mcp', + server_name: sanitizers.sanitizeServerName(props.server_name), + error_type: props.error_type, + retry_attempt: props.retry_attempt, + }, + }), + + // Slash Command event builders + slashCommandSelected: (props: SlashCommandSelectedProperties) => ({ + event: ANALYTICS_EVENTS.SLASH_COMMAND_SELECTED, + properties: { + category: 'slash_command', + command_name: sanitizers.sanitizeCommandName(props.command_name), + selection_method: props.selection_method, + }, + }), + + slashCommandExecuted: (props: SlashCommandExecutedProperties) => ({ + event: ANALYTICS_EVENTS.SLASH_COMMAND_EXECUTED, + properties: { + category: 'slash_command', + command_name: sanitizers.sanitizeCommandName(props.command_name), + parameters_count: props.parameters_count, + execution_time_ms: props.execution_time_ms, + }, + }), + + slashCommandCreated: (props: SlashCommandCreatedProperties) => ({ + event: ANALYTICS_EVENTS.SLASH_COMMAND_CREATED, + properties: { + category: 'slash_command', + ...props, + }, + }), + + // Error and Performance event builders + apiError: (props: APIErrorProperties) => ({ + event: ANALYTICS_EVENTS.API_ERROR, + properties: { + category: 'error', + endpoint: sanitizers.sanitizeEndpoint(props.endpoint), + error_code: props.error_code, + retry_count: props.retry_count, + response_time_ms: props.response_time_ms, + }, + }), + + uiError: (props: UIErrorProperties) => ({ + event: ANALYTICS_EVENTS.UI_ERROR, + properties: { + category: 'error', + ...props, + }, + }), + + performanceBottleneck: (props: PerformanceBottleneckProperties) => ({ + event: ANALYTICS_EVENTS.PERFORMANCE_BOTTLENECK, + properties: { + category: 'performance', + ...props, + }, + }), + + memoryWarning: (props: MemoryWarningProperties) => ({ + event: ANALYTICS_EVENTS.MEMORY_WARNING, + properties: { + category: 'performance', + ...props, + }, + }), + + // User journey event builders + journeyMilestone: (props: UserJourneyProperties) => ({ + event: ANALYTICS_EVENTS.JOURNEY_MILESTONE, + properties: { + category: 'user_journey', + ...props, + }, + }), + + // Enhanced prompt submission with more context + enhancedPromptSubmitted: (props: EnhancedPromptSubmittedProperties) => ({ + event: ANALYTICS_EVENTS.PROMPT_SUBMITTED, + properties: { + category: 'session', + prompt_length: props.prompt_length, + model: props.model, + has_attachments: props.has_attachments, + source: props.source, + word_count: props.word_count, + conversation_depth: props.conversation_depth, + prompt_complexity: props.prompt_complexity, + contains_code: props.contains_code, + language_detected: props.language_detected, + session_age_ms: props.session_age_ms, + }, + }), + + // Enhanced tool execution with more context + enhancedToolExecuted: (props: EnhancedToolExecutedProperties) => ({ + event: ANALYTICS_EVENTS.TOOL_EXECUTED, + properties: { + category: 'session', + tool_name: sanitizers.sanitizeToolName(props.tool_name), + execution_time_ms: props.execution_time_ms, + success: props.success, + error_message: props.error_message ? sanitizers.sanitizeErrorMessage(props.error_message) : undefined, + tool_category: props.tool_category, + consecutive_failures: props.consecutive_failures, + retry_attempted: props.retry_attempted, + input_size_bytes: props.input_size_bytes, + output_size_bytes: props.output_size_bytes, + }, + }), + + // Enhanced error tracking + enhancedError: (props: EnhancedErrorProperties) => ({ + event: ANALYTICS_EVENTS.ERROR_OCCURRED, + properties: { + category: 'error', + error_type: props.error_type, + error_code: props.error_code, + error_message: props.error_message ? sanitizers.sanitizeErrorMessage(props.error_message) : undefined, + context: props.context, + user_action_before_error: props.user_action_before_error, + recovery_attempted: props.recovery_attempted, + recovery_successful: props.recovery_successful, + error_frequency: props.error_frequency, + stack_trace_hash: props.stack_trace_hash, + }, + }), + + // Session engagement + sessionEngagement: (props: SessionEngagementProperties) => ({ + event: ANALYTICS_EVENTS.SESSION_ENGAGEMENT, + properties: { + category: 'engagement', + ...props, + }, + }), + + // Feature discovery + featureDiscovered: (props: FeatureDiscoveryProperties) => ({ + event: ANALYTICS_EVENTS.FEATURE_DISCOVERED, + properties: { + category: 'feature_adoption', + ...props, + }, + }), + + // Output quality + outputRegenerated: (props: OutputQualityProperties) => ({ + event: ANALYTICS_EVENTS.OUTPUT_REGENERATED, + properties: { + category: 'quality', + ...props, + }, + }), + + // Conversation abandoned + conversationAbandoned: (reason: string, messagesCount: number) => ({ + event: ANALYTICS_EVENTS.CONVERSATION_ABANDONED, + properties: { + category: 'quality', + reason, + messages_count: messagesCount, + }, + }), + + // Suggestion tracking + suggestionAccepted: (props: SuggestionProperties) => ({ + event: ANALYTICS_EVENTS.SUGGESTION_ACCEPTED, + properties: { + category: 'quality', + ...props, + }, + }), + + suggestionRejected: (props: SuggestionProperties) => ({ + event: ANALYTICS_EVENTS.SUGGESTION_REJECTED, + properties: { + category: 'quality', + ...props, + }, + }), + + // Resource usage + resourceUsageHigh: (props: ResourceUsageProperties) => ({ + event: ANALYTICS_EVENTS.RESOURCE_USAGE_HIGH, + properties: { + category: 'performance', + ...props, + }, + }), + + resourceUsageSampled: (props: ResourceUsageProperties) => ({ + event: ANALYTICS_EVENTS.RESOURCE_USAGE_SAMPLED, + properties: { + category: 'performance', + ...props, + }, + }), + + // Feature adoption + featureAdopted: (props: FeatureAdoptionProperties) => ({ + event: ANALYTICS_EVENTS.FEATURE_ADOPTED, + properties: { + category: 'feature_adoption', + ...props, + }, + }), + + featureCombination: (props: FeatureCombinationProperties) => ({ + event: ANALYTICS_EVENTS.FEATURE_COMBINATION, + properties: { + category: 'feature_adoption', + ...props, + }, + }), + + // AI interactions + aiInteraction: (props: AIInteractionProperties) => ({ + event: ANALYTICS_EVENTS.AI_INTERACTION, + properties: { + category: 'ai', + ...props, + }, + }), + + promptPattern: (props: PromptPatternProperties) => ({ + event: ANALYTICS_EVENTS.PROMPT_PATTERN, + properties: { + category: 'ai', + ...props, + }, + }), + + // Workflow tracking + workflowStarted: (props: WorkflowProperties) => ({ + event: ANALYTICS_EVENTS.WORKFLOW_STARTED, + properties: { + category: 'workflow', + ...props, + }, + }), + + workflowCompleted: (props: WorkflowProperties) => ({ + event: ANALYTICS_EVENTS.WORKFLOW_COMPLETED, + properties: { + category: 'workflow', + ...props, + }, + }), + + workflowAbandoned: (props: WorkflowProperties) => ({ + event: ANALYTICS_EVENTS.WORKFLOW_ABANDONED, + properties: { + category: 'workflow', + ...props, + }, + }), + + // Network performance + networkPerformance: (props: NetworkPerformanceProperties) => ({ + event: ANALYTICS_EVENTS.NETWORK_PERFORMANCE, + properties: { + category: 'network', + endpoint_type: props.endpoint_type, + latency_ms: props.latency_ms, + payload_size_bytes: props.payload_size_bytes, + connection_quality: props.connection_quality, + retry_count: props.retry_count, + circuit_breaker_triggered: props.circuit_breaker_triggered, + }, + }), + + networkFailure: (props: NetworkPerformanceProperties) => ({ + event: ANALYTICS_EVENTS.NETWORK_FAILURE, + properties: { + category: 'network', + endpoint_type: props.endpoint_type, + latency_ms: props.latency_ms, + payload_size_bytes: props.payload_size_bytes, + connection_quality: props.connection_quality, + retry_count: props.retry_count, + circuit_breaker_triggered: props.circuit_breaker_triggered, + }, + }), }; // Sanitization helpers to remove PII @@ -147,4 +673,28 @@ export const sanitizers = { // Only keep the type, remove custom names return name.split('-')[0] || 'custom'; }, -}; \ No newline at end of file + + // Sanitize tool names to remove any user-specific info + sanitizeToolName: (name: string): string => { + // Remove any path-like structures + return name.replace(/\/[\w\-\/\.]+/g, '').toLowerCase(); + }, + + // Sanitize server names to remove any user-specific info + sanitizeServerName: (name: string): string => { + // Keep only the type or first part + return name.split(/[\-_]/)[0] || 'custom'; + }, + + // Sanitize command names + sanitizeCommandName: (name: string): string => { + // Remove any custom prefixes or user-specific parts + return name.replace(/^custom-/, '').split('-')[0] || 'custom'; + }, + + // Sanitize API endpoints + sanitizeEndpoint: (endpoint: string): string => { + // Remove any dynamic IDs or user-specific parts + return endpoint.replace(/\/\d+/g, '/:id').replace(/\/[\w\-]{20,}/g, '/:id'); + }, +}; diff --git a/src/lib/analytics/index.ts b/src/lib/analytics/index.ts index fcbad90..6c3d917 100644 --- a/src/lib/analytics/index.ts +++ b/src/lib/analytics/index.ts @@ -11,6 +11,7 @@ import type { export * from './types'; export * from './events'; export { ConsentManager } from './consent'; +export { ResourceMonitor, resourceMonitor } from './resourceMonitor'; class AnalyticsService { private static instance: AnalyticsService; @@ -19,6 +20,7 @@ class AnalyticsService { private config: AnalyticsConfig; private eventQueue: AnalyticsEvent[] = []; private flushInterval: NodeJS.Timeout | null = null; + private currentScreen: string = 'app_start'; private constructor() { this.consentManager = ConsentManager.getInstance(); @@ -66,9 +68,11 @@ class AnalyticsService { try { posthog.init(this.config.apiKey, { api_host: this.config.apiHost, - defaults: '2025-05-24', - capture_exceptions: true, - debug: import.meta.env.MODE === 'development', + capture_pageview: false, // Disable automatic pageview capture + capture_pageleave: false, // Disable automatic pageleave + bootstrap: { + distinctID: settings.userId, + }, persistence: this.config.persistence, autocapture: this.config.autocapture, disable_session_recording: this.config.disable_session_recording, @@ -78,6 +82,13 @@ class AnalyticsService { ph.identify(settings.userId, { anonymous: true, consent_date: settings.consentDate, + app_type: 'desktop', + app_name: 'claudia', + }); + + // Set initial screen + ph.capture('$screen', { + $screen_name: 'app_start', }); // Opt in since user has consented @@ -115,6 +126,17 @@ class AnalyticsService { } } + setScreen(screenName: string): void { + this.currentScreen = screenName; + + // Track screen view in PostHog + if (typeof posthog !== 'undefined' && typeof posthog.capture === 'function') { + posthog.capture('$screen', { + $screen_name: screenName, + }); + } + } + track(eventName: EventName | string, properties?: Record): void { // Check if analytics is enabled if (!this.consentManager.isEnabled()) { @@ -124,10 +146,17 @@ class AnalyticsService { // Sanitize properties to remove PII const sanitizedProperties = this.sanitizeProperties(properties || {}); + // Add screen context to all events + const enhancedProperties = { + ...sanitizedProperties, + screen_name: this.currentScreen, + app_context: 'claudia_desktop', + }; + // Create event const event: AnalyticsEvent = { event: eventName, - properties: sanitizedProperties, + properties: enhancedProperties, timestamp: Date.now(), sessionId: this.consentManager.getSessionId(), userId: this.consentManager.getUserId(), @@ -205,6 +234,7 @@ class AnalyticsService { ...event.properties, $session_id: event.sessionId, timestamp: event.timestamp, + $current_url: `claudia://${event.properties?.screen_name || 'unknown'}`, }); } }); @@ -248,3 +278,74 @@ export const analytics = AnalyticsService.getInstance(); // Export for direct usage export default analytics; + +/** + * Performance tracking utility for better insights + */ +export class PerformanceTracker { + private static performanceData: Map = new Map(); + + /** + * Record a performance metric + * Automatically tracks percentiles when enough data is collected + */ + static recordMetric(operation: string, duration: number): void { + if (!this.performanceData.has(operation)) { + this.performanceData.set(operation, []); + } + + const data = this.performanceData.get(operation)!; + data.push(duration); + + // Keep last 100 measurements for memory efficiency + if (data.length > 100) { + data.shift(); + } + + // Track percentiles every 10 measurements + if (data.length >= 10 && data.length % 10 === 0) { + const sorted = [...data].sort((a, b) => a - b); + const p50 = sorted[Math.floor(sorted.length * 0.5)]; + const p95 = sorted[Math.floor(sorted.length * 0.95)]; + const p99 = sorted[Math.floor(sorted.length * 0.99)]; + + analytics.track('performance_percentiles', { + operation, + p50, + p95, + p99, + sample_size: data.length, + min: sorted[0], + max: sorted[sorted.length - 1], + avg: data.reduce((a, b) => a + b, 0) / data.length, + }); + } + } + + /** + * Get current statistics for an operation + */ + static getStats(operation: string): { p50: number; p95: number; p99: number; count: number } | null { + const data = this.performanceData.get(operation); + if (!data || data.length === 0) return null; + + const sorted = [...data].sort((a, b) => a - b); + return { + p50: sorted[Math.floor(sorted.length * 0.5)], + p95: sorted[Math.floor(sorted.length * 0.95)], + p99: sorted[Math.floor(sorted.length * 0.99)], + count: data.length, + }; + } + + /** + * Clear data for an operation or all operations + */ + static clear(operation?: string): void { + if (operation) { + this.performanceData.delete(operation); + } else { + this.performanceData.clear(); + } + } +} diff --git a/src/lib/analytics/types.ts b/src/lib/analytics/types.ts index 9cecf68..90d5d74 100644 --- a/src/lib/analytics/types.ts +++ b/src/lib/analytics/types.ts @@ -48,7 +48,58 @@ export type EventName = | 'slash_command_used' | 'settings_changed' | 'app_started' - | 'app_closed'; + | 'app_closed' + // New session events + | 'prompt_submitted' + | 'session_stopped' + | 'checkpoint_created' + | 'checkpoint_restored' + | 'tool_executed' + // New agent events + | 'agent_started' + | 'agent_progress' + | 'agent_error' + // New MCP events + | 'mcp_server_added' + | 'mcp_server_removed' + | 'mcp_tool_invoked' + | 'mcp_connection_error' + // New slash command events + | 'slash_command_selected' + | 'slash_command_executed' + | 'slash_command_created' + // New error and performance events + | 'api_error' + | 'ui_error' + | 'performance_bottleneck' + | 'memory_warning' + // User journey events + | 'journey_milestone' + | 'user_retention' + // AI interaction events + | 'ai_interaction' + | 'prompt_pattern' + // Quality events + | 'output_regenerated' + | 'conversation_abandoned' + | 'suggestion_accepted' + | 'suggestion_rejected' + // Workflow events + | 'workflow_started' + | 'workflow_completed' + | 'workflow_abandoned' + // Feature adoption events + | 'feature_discovered' + | 'feature_adopted' + | 'feature_combination' + // Resource usage events + | 'resource_usage_high' + | 'resource_usage_sampled' + // Network performance events + | 'network_performance' + | 'network_failure' + // Engagement events + | 'session_engagement'; export interface FeatureUsageProperties { feature: string; @@ -99,4 +150,296 @@ export interface PerformanceMetrics { memory_usage_mb?: number; api_response_time_ms?: number; render_time_ms?: number; -} \ No newline at end of file +} + +// Claude Code Session event properties +export interface PromptSubmittedProperties { + prompt_length: number; + model: string; + has_attachments: boolean; + source: 'keyboard' | 'button'; + word_count: number; +} + +export interface SessionStoppedProperties { + duration_ms: number; + messages_count: number; + reason: 'user_stopped' | 'error' | 'completed'; +} + +// Enhanced session stopped properties for detailed analytics +export interface EnhancedSessionStoppedProperties extends SessionStoppedProperties { + // Timing metrics + time_to_first_message_ms?: number; + average_response_time_ms?: number; + idle_time_ms?: number; + + // Interaction metrics + prompts_sent: number; + tools_executed: number; + tools_failed: number; + files_created: number; + files_modified: number; + files_deleted: number; + + // Content metrics + total_tokens_used?: number; + code_blocks_generated?: number; + errors_encountered: number; + + // Session context + model: string; + has_checkpoints: boolean; + checkpoint_count?: number; + was_resumed: boolean; + + // Agent context (if applicable) + agent_type?: string; + agent_name?: string; + agent_success?: boolean; + + // Stop context + stop_source: 'user_button' | 'keyboard_shortcut' | 'timeout' | 'error' | 'completed'; + final_state: 'success' | 'partial' | 'failed' | 'cancelled'; + has_pending_prompts: boolean; + pending_prompts_count?: number; +} + +export interface CheckpointCreatedProperties { + checkpoint_number: number; + session_duration_at_checkpoint: number; +} + +export interface CheckpointRestoredProperties { + checkpoint_id: string; + time_since_checkpoint_ms: number; +} + +export interface ToolExecutedProperties { + tool_name: string; + execution_time_ms: number; + success: boolean; + error_message?: string; +} + +// Enhanced Agent properties +export interface AgentStartedProperties { + agent_type: string; + agent_name?: string; + has_custom_prompt: boolean; +} + +export interface AgentProgressProperties { + step_number: number; + step_type: string; + duration_ms: number; + agent_type: string; +} + +export interface AgentErrorProperties { + error_type: string; + error_stage: string; + retry_count: number; + agent_type: string; +} + +// MCP properties +export interface MCPServerAddedProperties { + server_type: string; + configuration_method: 'manual' | 'preset' | 'import'; +} + +export interface MCPServerRemovedProperties { + server_name: string; + was_connected: boolean; +} + +export interface MCPToolInvokedProperties { + server_name: string; + tool_name: string; + invocation_source: 'user' | 'agent' | 'suggestion'; +} + +export interface MCPConnectionErrorProperties { + server_name: string; + error_type: string; + retry_attempt: number; +} + +// Slash Command properties +export interface SlashCommandSelectedProperties { + command_name: string; + selection_method: 'click' | 'keyboard' | 'autocomplete'; +} + +export interface SlashCommandExecutedProperties { + command_name: string; + parameters_count: number; + execution_time_ms: number; +} + +export interface SlashCommandCreatedProperties { + command_type: 'custom' | 'imported'; + has_parameters: boolean; +} + +// Error and Performance properties +export interface APIErrorProperties { + endpoint: string; + error_code: string | number; + retry_count: number; + response_time_ms: number; +} + +export interface UIErrorProperties { + component_name: string; + error_type: string; + user_action?: string; +} + +export interface PerformanceBottleneckProperties { + operation_type: string; + duration_ms: number; + data_size?: number; + threshold_exceeded: boolean; +} + +export interface MemoryWarningProperties { + component: string; + memory_mb: number; + threshold_exceeded: boolean; + gc_count?: number; +} + +// User Journey properties +export interface UserJourneyProperties { + journey_stage: 'onboarding' | 'first_chat' | 'first_agent' | 'power_user'; + milestone_reached?: string; + time_to_milestone_ms?: number; +} + +// Enhanced prompt properties +export interface EnhancedPromptSubmittedProperties extends PromptSubmittedProperties { + conversation_depth: number; + prompt_complexity: 'simple' | 'moderate' | 'complex'; + contains_code: boolean; + language_detected?: string; + session_age_ms: number; +} + +// Enhanced tool properties +export interface EnhancedToolExecutedProperties extends ToolExecutedProperties { + tool_category: 'file' | 'search' | 'system' | 'custom'; + consecutive_failures?: number; + retry_attempted: boolean; + input_size_bytes?: number; + output_size_bytes?: number; +} + +// Enhanced error properties +export interface EnhancedErrorProperties extends ErrorProperties { + user_action_before_error?: string; + recovery_attempted: boolean; + recovery_successful?: boolean; + error_frequency: number; + stack_trace_hash?: string; +} + +// Session engagement properties +export interface SessionEngagementProperties { + session_duration_ms: number; + messages_sent: number; + tools_used: string[]; + files_modified: number; + engagement_score: number; +} + +// Feature discovery properties +export interface FeatureDiscoveryProperties { + feature_name: string; + discovery_method: 'organic' | 'prompted' | 'documentation'; + time_to_first_use_ms: number; + initial_success: boolean; +} + +// Output quality properties +export interface OutputQualityProperties { + regeneration_count: number; + modification_requested: boolean; + final_acceptance: boolean; + time_to_acceptance_ms: number; +} + +// Resource usage properties +export interface ResourceUsageProperties { + cpu_usage_percent?: number; + memory_usage_mb: number; + disk_io_mb?: number; + network_requests_count: number; + cache_hit_rate?: number; + active_connections: number; +} + +// Feature adoption properties +export interface FeatureAdoptionProperties { + feature: string; + adoption_stage: 'discovered' | 'tried' | 'adopted' | 'abandoned'; + usage_count: number; + days_since_first_use: number; + usage_trend: 'increasing' | 'stable' | 'decreasing'; +} + +// Feature combination properties +export interface FeatureCombinationProperties { + primary_feature: string; + secondary_feature: string; + combination_frequency: number; + workflow_efficiency_gain?: number; +} + +// AI interaction properties +export interface AIInteractionProperties { + model: string; + request_tokens: number; + response_tokens: number; + response_quality_score?: number; + context_switches: number; + clarification_requests: number; +} + +// Prompt pattern properties +export interface PromptPatternProperties { + prompt_category: string; + prompt_effectiveness: 'high' | 'medium' | 'low'; + required_iterations: number; + final_satisfaction: boolean; +} + +// Workflow properties +export interface WorkflowProperties { + workflow_type: string; + steps_completed: number; + total_steps: number; + duration_ms: number; + interruptions: number; + completion_rate: number; + tools_used: string[]; +} + +// Network performance properties +export interface NetworkPerformanceProperties { + endpoint_type: 'mcp' | 'api' | 'webhook'; + latency_ms: number; + payload_size_bytes: number; + connection_quality: 'excellent' | 'good' | 'poor'; + retry_count: number; + circuit_breaker_triggered: boolean; +} + +// Suggestion properties +export interface SuggestionProperties { + suggestion_type: string; + suggestion_source: string; + accepted: boolean; + response_time_ms: number; +}