Compare commits

..

1 Commits

Author SHA1 Message Date
Alex ec0d5d7cb7
Merge 2e4a49feb6 into 2444817455 2025-10-22 22:37:40 +00:00
3 changed files with 15 additions and 76 deletions

View File

@ -143,10 +143,16 @@ func handleUpdateSessionNicknameRPC(params map[string]any, session *Session) (an
return nil, errors.New("permission denied: can only update own nickname") return nil, errors.New("permission denied: can only update own nickname")
} }
if err := sessionManager.UpdateSessionNickname(sessionID, nickname); err != nil { // Check nickname uniqueness
return nil, err allSessions := sessionManager.GetAllSessions()
for _, existingSession := range allSessions {
if existingSession.ID != sessionID && existingSession.Nickname == nickname {
return nil, fmt.Errorf("nickname '%s' is already in use by another session", nickname)
}
} }
targetSession.Nickname = nickname
// If session is pending and approval is required, send the approval request now that we have a nickname // If session is pending and approval is required, send the approval request now that we have a nickname
if targetSession.Mode == SessionModePending && currentSessionSettings != nil && currentSessionSettings.RequireApproval { if targetSession.Mode == SessionModePending && currentSessionSettings != nil && currentSessionSettings.RequireApproval {
if primary := sessionManager.GetPrimarySession(); primary != nil { if primary := sessionManager.GetPrimarySession(); primary != nil {

View File

@ -33,27 +33,7 @@ func (sm *SessionManager) attemptEmergencyPromotion(ctx emergencyPromotionContex
Str("triggerSessionID", ctx.triggerSessionID). Str("triggerSessionID", ctx.triggerSessionID).
Str("triggerReason", ctx.triggerReason). Str("triggerReason", ctx.triggerReason).
Msg("Bypassing emergency promotion rate limits - no primary exists") Msg("Bypassing emergency promotion rate limits - no primary exists")
promotedSessionID := sm.findMostTrustedSessionForEmergency()
// Find best session, excluding the specified session if provided
var promotedSessionID string
if excludeSessionID != "" {
bestSessionID := ""
bestScore := -1
for id, session := range sm.sessions {
if id != excludeSessionID &&
!sm.isSessionBlacklisted(id) &&
(session.Mode == SessionModeObserver || session.Mode == SessionModeQueued) {
score := sm.getSessionTrustScore(id)
if score > bestScore {
bestScore = score
bestSessionID = id
}
}
}
promotedSessionID = bestSessionID
} else {
promotedSessionID = sm.findMostTrustedSessionForEmergency()
}
return promotedSessionID, true, false return promotedSessionID, true, false
} }
@ -286,12 +266,6 @@ func (sm *SessionManager) handlePrimarySessionTimeout(now time.Time) bool {
primary.Mode = SessionModeObserver primary.Mode = SessionModeObserver
sm.primarySessionID = "" sm.primarySessionID = ""
sm.logger.Info().
Str("sessionID", timedOutSessionID).
Dur("inactiveFor", now.Sub(primary.LastActive)).
Dur("timeout", currentTimeout).
Msg("Primary session timed out due to inactivity - demoted to observer")
ctx := emergencyPromotionContext{ ctx := emergencyPromotionContext{
triggerSessionID: timedOutSessionID, triggerSessionID: timedOutSessionID,
triggerReason: "timeout", triggerReason: "timeout",
@ -300,8 +274,7 @@ func (sm *SessionManager) handlePrimarySessionTimeout(now time.Time) bool {
promotedSessionID, isEmergency, shouldSkip := sm.attemptEmergencyPromotion(ctx, timedOutSessionID) promotedSessionID, isEmergency, shouldSkip := sm.attemptEmergencyPromotion(ctx, timedOutSessionID)
if shouldSkip { if shouldSkip {
sm.logger.Info().Msg("Promotion skipped after timeout - session demoted but no promotion") return false
return true // Still need to broadcast the demotion
} }
if promotedSessionID != "" { if promotedSessionID != "" {
@ -334,17 +307,8 @@ func (sm *SessionManager) handlePrimarySessionTimeout(now time.Time) bool {
Bool("isEmergencyPromotion", isEmergency). Bool("isEmergencyPromotion", isEmergency).
Msg("Auto-promoted session after primary timeout") Msg("Auto-promoted session after primary timeout")
return true return true
} else {
sm.logger.Error().Err(err).
Str("timedOutSessionID", timedOutSessionID).
Str("promotedSessionID", promotedSessionID).
Msg("Failed to promote session after timeout - primary demoted")
return true // Still broadcast the demotion even if promotion failed
} }
} }
sm.logger.Info(). return false
Str("timedOutSessionID", timedOutSessionID).
Msg("Primary session timed out - demoted to observer, no eligible sessions to promote")
return true // Broadcast the demotion even if no promotion
} }

View File

@ -195,7 +195,7 @@ func (sm *SessionManager) AddSession(session *Session, clientSettings *SessionSe
delete(sm.nicknameIndex, session.Nickname) delete(sm.nicknameIndex, session.Nickname)
} }
} }
sm.logger.Error().Interface("panic", r).Str("sessionID", session.ID).Msg("Recovered from panic in AddSession") panic(r)
} }
}() }()
@ -987,35 +987,6 @@ func (sm *SessionManager) UpdateLastActive(sessionID string) {
sm.mu.Unlock() sm.mu.Unlock()
} }
// UpdateSessionNickname atomically updates a session's nickname with uniqueness check
func (sm *SessionManager) UpdateSessionNickname(sessionID, nickname string) error {
sm.mu.Lock()
defer sm.mu.Unlock()
targetSession, exists := sm.sessions[sessionID]
if !exists {
return errors.New("session not found")
}
// Check nickname uniqueness under lock
if existingSession, nicknameInUse := sm.nicknameIndex[nickname]; nicknameInUse {
if existingSession.ID != sessionID {
return fmt.Errorf("nickname '%s' is already in use by another session", nickname)
}
}
// Remove old nickname from index
if targetSession.Nickname != "" {
delete(sm.nicknameIndex, targetSession.Nickname)
}
// Update nickname and index atomically
targetSession.Nickname = nickname
sm.nicknameIndex[nickname] = targetSession
return nil
}
// Internal helper methods // Internal helper methods
// validateSinglePrimary ensures there's only one primary session and fixes any inconsistencies // validateSinglePrimary ensures there's only one primary session and fixes any inconsistencies
@ -1393,11 +1364,9 @@ func (sm *SessionManager) getSessionTrustScore(sessionID string) int {
// Longer session duration = more trust (up to 100 points for 100+ minutes) // Longer session duration = more trust (up to 100 points for 100+ minutes)
sessionAge := now.Sub(session.CreatedAt) sessionAge := now.Sub(session.CreatedAt)
sessionAgeMinutes := sessionAge.Minutes() score += int(sessionAge.Minutes())
if sessionAgeMinutes > 100 { if score > 100 {
score += 100 score = 100 // Cap age bonus at 100 points
} else {
score += int(sessionAgeMinutes)
} }
// Recently successful primary sessions get higher trust // Recently successful primary sessions get higher trust