fix(P1): 6 priority-1 bug fixes for autonomous engine and mood system

#4  Sleep/mood desync — set_server_mood() now clears is_sleeping when
    mood changes away from 'asleep', preventing ghost-sleep state.

#5  Race condition in _check_and_act — added per-guild asyncio.Lock so
    overlapping ticks + message-triggered calls cannot fire concurrently.

#6  Class-level attrs on ServerConfig — sleepy_responses_left,
    angry_wakeup_timer, and forced_angry_until are now proper dataclass
    fields with defaults, so asdict()/from_dict() round-trip correctly.
    Also strips unknown keys in from_dict() to survive schema changes.

#7  Persistence decay_factor crash — initialise decay_factor = 1.0
    before the loop so empty-server or zero-downtime paths don't
    raise NameError.

#8  Double record_action — removed the redundant call in
    autonomous_tick_v2(); only _check_and_act records the action now.

#9  Engine mood desync — on_mood_change() is now called inside
    set_server_mood() (single source of truth) and removed from 4
    call-sites in api.py, moods.py, and server_manager wakeup task.
This commit is contained in:
2026-02-23 13:31:15 +02:00
parent 422366df4c
commit 0e4aebf353
6 changed files with 98 additions and 101 deletions

View File

@@ -4,7 +4,7 @@ import json
import os
import asyncio
from typing import Dict, List, Optional, Set
from dataclasses import dataclass, asdict
from dataclasses import dataclass, asdict, fields as dataclass_fields
from datetime import datetime, timedelta
import discord
from apscheduler.schedulers.asyncio import AsyncIOScheduler
@@ -39,9 +39,9 @@ class ServerConfig:
current_mood_description: str = ""
previous_mood_name: str = "neutral"
is_sleeping: bool = False
sleepy_responses_left: int = None
angry_wakeup_timer = None
forced_angry_until = None
sleepy_responses_left: Optional[int] = None
angry_wakeup_timer: Optional[float] = None # Unused, kept for structural completeness
forced_angry_until: Optional[str] = None # ISO format datetime string, or None
just_woken_up: bool = False
def to_dict(self):
@@ -64,6 +64,9 @@ class ServerConfig:
logger.warning(f"Failed to parse enabled_features string '{data['enabled_features']}': {e}")
# Fallback to default features
data['enabled_features'] = {"autonomous", "bedtime", "monday_video"}
# Strip any keys that aren't valid dataclass fields (forward-compat safety)
valid_fields = {f.name for f in dataclass_fields(cls)}
data = {k: v for k, v in data.items() if k in valid_fields}
return cls(**data)
class ServerManager:
@@ -255,7 +258,12 @@ class ServerManager:
return server.current_mood_name, server.current_mood_description
def set_server_mood(self, guild_id: int, mood_name: str, mood_description: str = None):
"""Set mood for a specific server"""
"""Set mood for a specific server.
Also handles:
- Syncing is_sleeping state (fix #4: sleep/mood desync)
- Notifying the autonomous engine (fix #9: engine mood desync)
"""
if guild_id not in self.servers:
return False
@@ -274,9 +282,24 @@ class ServerManager:
logger.error(f"Failed to load mood description for {mood_name}: {e}")
server.current_mood_description = f"I'm feeling {mood_name} today."
# Fix #4: Keep is_sleeping in sync with mood
# If mood changes away from 'asleep', clear sleeping state
if mood_name != "asleep" and server.is_sleeping:
server.is_sleeping = False
self.cancel_wakeup_task(guild_id)
logger.info(f"Cleared sleep state for server {server.guild_name} (mood changed to {mood_name})")
self.save_config()
logger.info(f"Server {server.guild_name} mood changed to: {mood_name}")
logger.debug(f"Mood description: {server.current_mood_description[:100]}{'...' if len(server.current_mood_description) > 100 else ''}")
# Fix #9: Always notify autonomous engine of mood change
try:
from utils.autonomous import on_mood_change
on_mood_change(guild_id, mood_name)
except Exception as e:
logger.error(f"Failed to notify autonomous engine of mood change to {mood_name}: {e}")
return True
def get_server_sleep_state(self, guild_id: int) -> bool:
@@ -321,13 +344,6 @@ class ServerManager:
self.set_server_sleep_state(guild_id, False)
self.set_server_mood(guild_id, "neutral")
# Notify autonomous engine
try:
from utils.autonomous import on_mood_change
on_mood_change(guild_id, "neutral")
except Exception as e:
logger.error(f"Failed to notify autonomous engine of wake-up: {e}")
# Update nickname
try:
from utils.moods import update_server_nickname