Spaces:
Paused
Paused
| // Enhanced EmotionAwareDecisionEngine – Phase 1 Week 3 | |
| // Multi-modal decision making with emotional awareness | |
| import { PalRepository } from '../../services/pal/palRepository.js'; | |
| import { unifiedMemorySystem } from './UnifiedMemorySystem.js'; | |
| import { McpContext } from '@widget-tdc/mcp-types'; | |
| import { QueryIntent } from '../autonomous/DecisionEngine.js'; | |
| export interface EmotionalState { | |
| stress: 'low' | 'medium' | 'high'; | |
| focus: 'shallow' | 'medium' | 'deep'; | |
| energy: 'low' | 'medium' | 'high'; | |
| mood: 'negative' | 'neutral' | 'positive'; | |
| } | |
| export interface Action { | |
| complexity: 'low' | 'medium' | 'high'; | |
| estimatedTime: number; // milliseconds | |
| depth: 'low' | 'medium' | 'high'; | |
| requiresFocus: boolean; | |
| } | |
| export interface Decision { | |
| action: Action; | |
| confidence: number; | |
| reasoning: string; | |
| emotionalFit: number; | |
| dataQuality: number; | |
| contextRelevance: number; | |
| emotionalState?: EmotionalState; | |
| } | |
| export class EmotionAwareDecisionEngine { | |
| private palRepo: PalRepository; | |
| constructor() { | |
| this.palRepo = new PalRepository(); | |
| } | |
| /** | |
| * Make emotion-aware decision based on query, emotional state, and context | |
| */ | |
| async makeDecision( | |
| query: string | QueryIntent, | |
| ctx: McpContext | |
| ): Promise<Decision> { | |
| // Get emotional state from PAL | |
| const emotionalState = await this.getEmotionalState(ctx.userId, ctx.orgId); | |
| // Normalize query: if QueryIntent, convert to string representation; if string, use as-is | |
| const queryStr = typeof query === 'string' | |
| ? query | |
| : `${query.operation || query.type} ${query.domain || ''} ${JSON.stringify(query.params || {})}`.trim(); | |
| // Convert to QueryIntent for methods that need structured data | |
| const queryIntent: QueryIntent = typeof query === 'string' | |
| ? { | |
| type: 'query', | |
| domain: 'general', | |
| operation: 'search', | |
| params: { query: queryStr } | |
| } | |
| : query; | |
| // Evaluate multi-modal scores | |
| const dataScore = await this.evaluateDataQuality(queryIntent, ctx); | |
| const emotionScore = this.evaluateEmotionalFit(this.queryToAction(queryIntent), emotionalState); | |
| const contextScore = await this.evaluateContextRelevance(queryIntent, ctx); | |
| // Calculate dynamic weights based on emotional state | |
| const weights = this.calculateDynamicWeights(emotionalState); | |
| // Fuse scores with weights | |
| const fusedScore = this.fusionDecision( | |
| { | |
| data: dataScore, | |
| emotion: emotionScore, | |
| context: contextScore | |
| }, | |
| weights | |
| ); | |
| // Determine action complexity based on emotional state | |
| const action = this.determineOptimalAction(queryIntent, emotionalState); | |
| const decision: Decision = { | |
| action, | |
| confidence: fusedScore, | |
| reasoning: this.generateReasoning(emotionalState, dataScore, emotionScore, contextScore), | |
| emotionalFit: emotionScore, | |
| dataQuality: dataScore, | |
| contextRelevance: contextScore, | |
| emotionalState | |
| }; | |
| return decision; | |
| } | |
| /** | |
| * Get emotional state from PAL repository | |
| */ | |
| private async getEmotionalState(userId: string, orgId: string): Promise<EmotionalState> { | |
| try { | |
| // Get recent PAL events to infer emotional state | |
| const recentEvents = this.palRepo.getRecentEvents(userId, orgId, 10); | |
| // Analyze events for stress indicators | |
| let stressLevel: 'low' | 'medium' | 'high' = 'low'; | |
| let focusLevel: 'shallow' | 'medium' | 'deep' = 'medium'; | |
| const energyLevel: 'low' | 'medium' | 'high' = 'medium'; | |
| const mood: 'negative' | 'neutral' | 'positive' = 'neutral'; | |
| if (Array.isArray(recentEvents)) { | |
| const stressEvents = recentEvents.filter((e: any) => | |
| e.event_type === 'stress' || e.detected_stress_level | |
| ); | |
| if (stressEvents.length > 0) { | |
| const avgStress = stressEvents.reduce((sum: number, e: any) => { | |
| const level = e.detected_stress_level || e.stress_level || 0; | |
| return sum + level; | |
| }, 0) / stressEvents.length; | |
| if (avgStress > 7) stressLevel = 'high'; | |
| else if (avgStress > 4) stressLevel = 'medium'; | |
| } | |
| // Check focus windows | |
| const focusWindows = await this.palRepo.getFocusWindows(userId, orgId); | |
| const now = new Date(); | |
| const currentHour = now.getHours(); | |
| const currentDay = now.getDay(); | |
| const inFocusWindow = focusWindows.some((fw: any) => | |
| fw.weekday === currentDay && | |
| currentHour >= fw.start_hour && | |
| currentHour < fw.end_hour | |
| ); | |
| if (inFocusWindow) { | |
| focusLevel = 'deep'; | |
| } | |
| } | |
| return { | |
| stress: stressLevel, | |
| focus: focusLevel, | |
| energy: energyLevel, | |
| mood | |
| }; | |
| } catch (error) { | |
| console.warn('Failed to get emotional state, using defaults:', error); | |
| return { | |
| stress: 'low', | |
| focus: 'medium', | |
| energy: 'medium', | |
| mood: 'neutral' | |
| }; | |
| } | |
| } | |
| /** | |
| * Evaluate data quality score | |
| */ | |
| private async evaluateDataQuality(query: QueryIntent, _ctx: McpContext): Promise<number> { | |
| try { | |
| // Check system health - healthy systems provide better data | |
| const health = await unifiedMemorySystem.analyzeSystemHealth(); | |
| const baseScore = health.globalHealth; | |
| // Adjust based on query complexity | |
| const complexity = this.estimateQueryComplexity(query); | |
| const complexityPenalty = complexity === 'high' ? 0.1 : complexity === 'medium' ? 0.05 : 0; | |
| return Math.max(0, Math.min(1, baseScore - complexityPenalty)); | |
| } catch { | |
| return 0.8; // Default good score | |
| } | |
| } | |
| /** | |
| * Evaluate emotional fit of action | |
| */ | |
| private evaluateEmotionalFit(action: Action, emotion: EmotionalState): number { | |
| let score = 0.5; // Base neutral score | |
| // Stress-aware routing | |
| if (emotion.stress === 'high') { | |
| // Prefer simple, fast actions | |
| if (action.complexity === 'low' && action.estimatedTime < 1000) { | |
| score = 1.0; | |
| } else if (action.complexity === 'high') { | |
| score = 0.2; | |
| } else { | |
| score = 0.6; | |
| } | |
| } else if (emotion.stress === 'low') { | |
| // Can handle more complexity | |
| if (action.complexity === 'high' && action.depth === 'high') { | |
| score = 0.9; | |
| } else { | |
| score = 0.7; | |
| } | |
| } | |
| // Focus-aware routing | |
| if (emotion.focus === 'deep') { | |
| // Allow complex, deep tasks | |
| if (action.depth === 'high' && action.requiresFocus) { | |
| score = Math.max(score, 1.0); | |
| } | |
| } else if (emotion.focus === 'shallow') { | |
| // Prefer simpler tasks | |
| if (action.complexity === 'high') { | |
| score = Math.min(score, 0.4); | |
| } | |
| } | |
| // Energy-aware routing | |
| if (emotion.energy === 'low') { | |
| // Prefer low-effort tasks | |
| if (action.complexity === 'low' && action.estimatedTime < 500) { | |
| score = Math.max(score, 0.9); | |
| } else { | |
| score = Math.min(score, 0.6); | |
| } | |
| } | |
| return Math.max(0, Math.min(1, score)); | |
| } | |
| /** | |
| * Evaluate context relevance | |
| */ | |
| private async evaluateContextRelevance(query: QueryIntent, ctx: McpContext): Promise<number> { | |
| try { | |
| // Check if query matches recent patterns | |
| const patterns = await unifiedMemorySystem.findHolographicPatterns(ctx); | |
| const queryText = JSON.stringify(query).toLowerCase(); | |
| const relevantPatterns = patterns.filter((p: any) => { | |
| const keyword = p.keyword?.toLowerCase() || ''; | |
| return keyword && queryText.includes(keyword); | |
| }); | |
| // More relevant patterns = higher score | |
| return Math.min(1, 0.5 + (relevantPatterns.length * 0.1)); | |
| } catch { | |
| return 0.7; // Default good relevance | |
| } | |
| } | |
| /** | |
| * Calculate dynamic weights based on emotional state | |
| */ | |
| private calculateDynamicWeights(emotion: EmotionalState): { | |
| data: number; | |
| emotion: number; | |
| context: number; | |
| } { | |
| // Base weights | |
| let dataWeight = 0.4; | |
| let emotionWeight = 0.3; | |
| let contextWeight = 0.3; | |
| // Adjust weights based on stress | |
| if (emotion.stress === 'high') { | |
| // Prioritize emotional fit when stressed | |
| emotionWeight = 0.5; | |
| dataWeight = 0.3; | |
| contextWeight = 0.2; | |
| } else if (emotion.stress === 'low') { | |
| // Prioritize data quality when relaxed | |
| dataWeight = 0.5; | |
| emotionWeight = 0.2; | |
| contextWeight = 0.3; | |
| } | |
| // Normalize | |
| const total = dataWeight + emotionWeight + contextWeight; | |
| return { | |
| data: dataWeight / total, | |
| emotion: emotionWeight / total, | |
| context: contextWeight / total | |
| }; | |
| } | |
| /** | |
| * Fuse scores with weights | |
| */ | |
| private fusionDecision( | |
| scores: { data: number; emotion: number; context: number }, | |
| weights: { data: number; emotion: number; context: number } | |
| ): number { | |
| return ( | |
| scores.data * weights.data + | |
| scores.emotion * weights.emotion + | |
| scores.context * weights.context | |
| ); | |
| } | |
| /** | |
| * Determine optimal action based on query and emotional state | |
| */ | |
| private determineOptimalAction(query: QueryIntent, emotion: EmotionalState): Action { | |
| // Estimate complexity from query | |
| const complexity = this.estimateQueryComplexity(query); | |
| // Estimate time (placeholder - would be more sophisticated) | |
| const estimatedTime = complexity === 'high' ? 2000 : complexity === 'medium' ? 1000 : 500; | |
| // Determine depth requirement | |
| const depth = complexity === 'high' ? 'high' : complexity === 'medium' ? 'medium' : 'low'; | |
| // Adjust based on emotional state | |
| let finalComplexity = complexity; | |
| if (emotion.stress === 'high' && complexity === 'high') { | |
| finalComplexity = 'medium'; // Reduce complexity when stressed | |
| } | |
| return { | |
| complexity: finalComplexity, | |
| estimatedTime, | |
| depth, | |
| requiresFocus: complexity === 'high' || emotion.focus === 'deep' | |
| }; | |
| } | |
| /** | |
| * Estimate query complexity | |
| */ | |
| private estimateQueryComplexity(query: QueryIntent): 'low' | 'medium' | 'high' { | |
| const queryStr = JSON.stringify(query).toLowerCase(); | |
| // Simple heuristics | |
| if (queryStr.includes('complex') || queryStr.includes('analyze') || queryStr.includes('deep')) { | |
| return 'high'; | |
| } | |
| if (queryStr.includes('search') || queryStr.includes('find') || queryStr.includes('get')) { | |
| return 'medium'; | |
| } | |
| return 'low'; | |
| } | |
| /** | |
| * Convert query to action representation | |
| */ | |
| private queryToAction(query: QueryIntent): Action { | |
| const complexity = this.estimateQueryComplexity(query); | |
| return { | |
| complexity, | |
| estimatedTime: complexity === 'high' ? 2000 : complexity === 'medium' ? 1000 : 500, | |
| depth: complexity === 'high' ? 'high' : complexity === 'medium' ? 'medium' : 'low', | |
| requiresFocus: complexity === 'high' | |
| }; | |
| } | |
| /** | |
| * Generate human-readable reasoning | |
| */ | |
| private generateReasoning( | |
| emotion: EmotionalState, | |
| dataScore: number, | |
| emotionScore: number, | |
| contextScore: number | |
| ): string { | |
| const parts: string[] = []; | |
| if (emotion.stress === 'high') { | |
| parts.push('User is experiencing high stress - prioritizing simple, fast actions'); | |
| } | |
| if (emotion.focus === 'deep') { | |
| parts.push('User is in deep focus mode - allowing complex tasks'); | |
| } | |
| if (dataScore > 0.8) { | |
| parts.push('High data quality available'); | |
| } | |
| if (emotionScore > 0.8) { | |
| parts.push('Action matches emotional state well'); | |
| } | |
| if (contextScore > 0.8) { | |
| parts.push('High context relevance'); | |
| } | |
| return parts.length > 0 | |
| ? parts.join('. ') | |
| : 'Balanced decision based on available information'; | |
| } | |
| } | |
| export const emotionAwareDecisionEngine = new EmotionAwareDecisionEngine(); | |