package main import ( "sync" "time" ) // EngagementState tracks whether a user is in an active conversation. type EngagementState int const ( Sleeping EngagementState = 0 Active EngagementState = 1 ) // engagementSession tracks a single user's active conversation. type engagementSession struct { userID string channelID string state EngagementState lastActive time.Time } // EngagementEngine manages active conversations per user+channel. // When a user @mentions the bot, they enter an Active session. // Their subsequent non-@mention messages in the same channel // are treated as continuations until a stop word or timeout. type EngagementEngine struct { mu sync.Mutex sessions map[string]*engagementSession timeout time.Duration } // NewEngagementEngine creates a new engine with the given inactivity timeout. func NewEngagementEngine() *EngagementEngine { return &EngagementEngine{ sessions: make(map[string]*engagementSession), timeout: 5 * time.Minute, } } // sessionKey builds a unique key per user+channel. func (e *EngagementEngine) sessionKey(userID, channelID string) string { return userID + ":" + channelID } // Wake marks a user+channel as actively engaged with the bot. func (e *EngagementEngine) Wake(userID, channelID string) { e.mu.Lock() defer e.mu.Unlock() key := e.sessionKey(userID, channelID) e.sessions[key] = &engagementSession{ userID: userID, channelID: channelID, state: Active, lastActive: time.Now(), } } // IsActive returns true if the user+channel is in an active conversation. func (e *EngagementEngine) IsActive(userID, channelID string) bool { e.mu.Lock() defer e.mu.Unlock() key := e.sessionKey(userID, channelID) s, exists := e.sessions[key] if !exists { return false } if time.Since(s.lastActive) > e.timeout { delete(e.sessions, key) return false } s.lastActive = time.Now() return true } // Sleep ends the active conversation for a user+channel. func (e *EngagementEngine) Sleep(userID, channelID string) { e.mu.Lock() defer e.mu.Unlock() key := e.sessionKey(userID, channelID) delete(e.sessions, key) }