Files
miku-discord/bot/utils/autonomous.py

397 lines
15 KiB
Python
Raw Normal View History

2025-12-07 17:15:09 +02:00
# autonomous.py (V2)
"""
Enhanced autonomous system that uses the autonomous_engine for true autonomy.
Integrates with legacy autonomous functions from autonomous_v1_legacy.py
"""
import asyncio
import time
from utils.autonomous_engine import autonomous_engine
from server_manager import server_manager
import globals
from utils.logger import get_logger
logger = get_logger('autonomous')
2025-12-07 17:15:09 +02:00
# Rate limiting: Track last action time per server to prevent rapid-fire
_last_action_execution = {} # guild_id -> timestamp
_MIN_ACTION_INTERVAL = 30 # Minimum 30 seconds between autonomous actions
# Pause state for voice sessions
_autonomous_paused = False
def pause_autonomous_system():
"""Pause autonomous message generation (called during voice sessions)"""
global _autonomous_paused
_autonomous_paused = True
logger.info("Autonomous system paused")
def resume_autonomous_system():
"""Resume autonomous message generation (called after voice sessions)"""
global _autonomous_paused
_autonomous_paused = False
logger.info("Autonomous system resumed")
2025-12-07 17:15:09 +02:00
async def autonomous_tick_v2(guild_id: int):
"""
New autonomous tick that uses context-aware decision making.
Replaces the random 10% chance with intelligent decision.
"""
# Check if autonomous is paused (voice session)
if _autonomous_paused:
logger.debug(f"[V2] Autonomous system paused (voice session active)")
return
2025-12-07 17:15:09 +02:00
# Rate limiting check
now = time.time()
if guild_id in _last_action_execution:
time_since_last = now - _last_action_execution[guild_id]
if time_since_last < _MIN_ACTION_INTERVAL:
logger.debug(f"[V2] Rate limit: Only {time_since_last:.0f}s since last action (need {_MIN_ACTION_INTERVAL}s)")
2025-12-07 17:15:09 +02:00
return
# Ask the engine if Miku should act (with optional debug logging)
action_type = autonomous_engine.should_take_action(guild_id, debug=globals.AUTONOMOUS_DEBUG)
if action_type is None:
# Engine decided not to act
return
logger.info(f"[V2] Autonomous engine decided to: {action_type} for server {guild_id}")
2025-12-07 17:15:09 +02:00
# Execute the action using legacy functions
from utils.autonomous_v1_legacy import (
miku_say_something_general_for_server,
miku_engage_random_user_for_server,
share_miku_tweet_for_server,
miku_detect_and_join_conversation_for_server
)
from utils.profile_picture_manager import profile_picture_manager
try:
if action_type == "general":
await miku_say_something_general_for_server(guild_id)
elif action_type == "engage_user":
await miku_engage_random_user_for_server(guild_id)
elif action_type == "share_tweet":
await share_miku_tweet_for_server(guild_id)
elif action_type == "join_conversation":
await miku_detect_and_join_conversation_for_server(guild_id)
elif action_type == "change_profile_picture":
# Get current mood for this server
mood, _ = server_manager.get_server_mood(guild_id)
logger.info(f"[V2] Changing profile picture (mood: {mood})")
2025-12-07 17:15:09 +02:00
result = await profile_picture_manager.change_profile_picture(mood=mood, debug=True)
if result["success"]:
logger.info(f"Profile picture changed successfully!")
2025-12-07 17:15:09 +02:00
else:
logger.warning(f"Profile picture change failed: {result.get('error')}")
2025-12-07 17:15:09 +02:00
# Record that action was taken
autonomous_engine.record_action(guild_id)
Implement Bipolar Mode: Dual persona arguments with webhooks, LLM arbiter, and persistent scoreboard Major Features: - Complete Bipolar Mode system allowing Regular Miku and Evil Miku to coexist and argue via webhooks - LLM arbiter system using neutral model to judge argument winners with detailed reasoning - Persistent scoreboard tracking wins, percentages, and last 50 results with timestamps and reasoning - Automatic mode switching based on argument winner - Webhook management per channel with profile pictures and display names - Progressive probability system for dynamic argument lengths (starts at 10%, increases 5% per exchange, min 4 exchanges) - Draw handling with penalty system (-5% end chance, continues argument) - Integration with autonomous system for random argument triggers Argument System: - MIN_EXCHANGES = 4, progressive end chance starting at 10% - Enhanced prompts for both personas (strategic, short, punchy responses 1-3 sentences) - Evil Miku triumphant victory messages with gloating and satisfaction - Regular Miku assertive defense (not passive, shows backbone) - Message-based argument starting (can respond to specific messages via ID) - Conversation history tracking per argument with special user_id - Full context queries (personality, lore, lyrics, last 8 messages) LLM Arbiter: - Decisive prompt emphasizing picking winners (draws should be rare) - Improved parsing with first-line exact matching and fallback counting - Debug logging for decision transparency - Arbiter reasoning stored in scoreboard history for review - Uses neutral TEXT_MODEL (not evil) for unbiased judgment Web UI & API: - Bipolar mode toggle button (only visible when evil mode is on) - Channel ID + Message ID input fields for argument triggering - Scoreboard display with win percentages and recent history - Manual argument trigger endpoint with string-based IDs - GET /bipolar-mode/scoreboard endpoint for stats retrieval - Real-time active arguments tracking (refreshes every 5 seconds) Prompt Optimizations: - All argument prompts limited to 1-3 sentences for impact - Evil Miku system prompt with variable response length guidelines - Removed walls of text, emphasizing brevity and precision - "Sometimes the cruelest response is the shortest one" Evil Miku Updates: - Added height to lore (15.8m tall, 10x bigger than regular Miku) - Height added to prompt facts for size-based belittling - More strategic and calculating personality in arguments Integration: - Bipolar mode state restoration on bot startup - Bot skips processing messages during active arguments - Autonomous system checks for bipolar triggers after actions - Import fixes (apply_evil_mode_changes/revert_evil_mode_changes) Technical Details: - State persistence via JSON (bipolar_mode_state.json, bipolar_webhooks.json, bipolar_scoreboard.json) - Webhook caching per guild with fallback creation - Event loop management with asyncio.create_task - Rate limiting and argument conflict prevention - Globals integration (BIPOLAR_MODE, BIPOLAR_WEBHOOKS, BIPOLAR_ARGUMENT_IN_PROGRESS, MOOD_EMOJIS) Files Changed: - bot/bot.py: Added bipolar mode restoration and argument-in-progress checks - bot/globals.py: Added bipolar mode state variables and mood emoji mappings - bot/utils/bipolar_mode.py: Complete 1106-line implementation - bot/utils/autonomous.py: Added bipolar argument trigger checks - bot/utils/evil_mode.py: Updated system prompt, added height info to lore/prompt - bot/api.py: Added bipolar mode endpoints (trigger, toggle, scoreboard) - bot/static/index.html: Added bipolar controls section with scoreboard - bot/memory/: Various DM conversation updates - bot/evil_miku_lore.txt: Added height description - bot/evil_miku_prompt.txt: Added height to facts, updated personality guidelines
2026-01-06 13:57:59 +02:00
# Record that action was taken
autonomous_engine.record_action(guild_id)
2025-12-07 17:15:09 +02:00
# Update rate limiter
_last_action_execution[guild_id] = time.time()
Implement Bipolar Mode: Dual persona arguments with webhooks, LLM arbiter, and persistent scoreboard Major Features: - Complete Bipolar Mode system allowing Regular Miku and Evil Miku to coexist and argue via webhooks - LLM arbiter system using neutral model to judge argument winners with detailed reasoning - Persistent scoreboard tracking wins, percentages, and last 50 results with timestamps and reasoning - Automatic mode switching based on argument winner - Webhook management per channel with profile pictures and display names - Progressive probability system for dynamic argument lengths (starts at 10%, increases 5% per exchange, min 4 exchanges) - Draw handling with penalty system (-5% end chance, continues argument) - Integration with autonomous system for random argument triggers Argument System: - MIN_EXCHANGES = 4, progressive end chance starting at 10% - Enhanced prompts for both personas (strategic, short, punchy responses 1-3 sentences) - Evil Miku triumphant victory messages with gloating and satisfaction - Regular Miku assertive defense (not passive, shows backbone) - Message-based argument starting (can respond to specific messages via ID) - Conversation history tracking per argument with special user_id - Full context queries (personality, lore, lyrics, last 8 messages) LLM Arbiter: - Decisive prompt emphasizing picking winners (draws should be rare) - Improved parsing with first-line exact matching and fallback counting - Debug logging for decision transparency - Arbiter reasoning stored in scoreboard history for review - Uses neutral TEXT_MODEL (not evil) for unbiased judgment Web UI & API: - Bipolar mode toggle button (only visible when evil mode is on) - Channel ID + Message ID input fields for argument triggering - Scoreboard display with win percentages and recent history - Manual argument trigger endpoint with string-based IDs - GET /bipolar-mode/scoreboard endpoint for stats retrieval - Real-time active arguments tracking (refreshes every 5 seconds) Prompt Optimizations: - All argument prompts limited to 1-3 sentences for impact - Evil Miku system prompt with variable response length guidelines - Removed walls of text, emphasizing brevity and precision - "Sometimes the cruelest response is the shortest one" Evil Miku Updates: - Added height to lore (15.8m tall, 10x bigger than regular Miku) - Height added to prompt facts for size-based belittling - More strategic and calculating personality in arguments Integration: - Bipolar mode state restoration on bot startup - Bot skips processing messages during active arguments - Autonomous system checks for bipolar triggers after actions - Import fixes (apply_evil_mode_changes/revert_evil_mode_changes) Technical Details: - State persistence via JSON (bipolar_mode_state.json, bipolar_webhooks.json, bipolar_scoreboard.json) - Webhook caching per guild with fallback creation - Event loop management with asyncio.create_task - Rate limiting and argument conflict prevention - Globals integration (BIPOLAR_MODE, BIPOLAR_WEBHOOKS, BIPOLAR_ARGUMENT_IN_PROGRESS, MOOD_EMOJIS) Files Changed: - bot/bot.py: Added bipolar mode restoration and argument-in-progress checks - bot/globals.py: Added bipolar mode state variables and mood emoji mappings - bot/utils/bipolar_mode.py: Complete 1106-line implementation - bot/utils/autonomous.py: Added bipolar argument trigger checks - bot/utils/evil_mode.py: Updated system prompt, added height info to lore/prompt - bot/api.py: Added bipolar mode endpoints (trigger, toggle, scoreboard) - bot/static/index.html: Added bipolar controls section with scoreboard - bot/memory/: Various DM conversation updates - bot/evil_miku_lore.txt: Added height description - bot/evil_miku_prompt.txt: Added height to facts, updated personality guidelines
2026-01-06 13:57:59 +02:00
# Check for bipolar argument trigger (only if bipolar mode is active)
try:
from utils.bipolar_mode import maybe_trigger_argument, is_bipolar_mode
if is_bipolar_mode():
server_config = server_manager.servers.get(guild_id)
if server_config and server_config.autonomous_channel_id:
channel = globals.client.get_channel(server_config.autonomous_channel_id)
if channel:
await maybe_trigger_argument(channel, globals.client, "Triggered after an autonomous action")
except Exception as bipolar_err:
logger.warning(f"Bipolar check error: {bipolar_err}")
Implement Bipolar Mode: Dual persona arguments with webhooks, LLM arbiter, and persistent scoreboard Major Features: - Complete Bipolar Mode system allowing Regular Miku and Evil Miku to coexist and argue via webhooks - LLM arbiter system using neutral model to judge argument winners with detailed reasoning - Persistent scoreboard tracking wins, percentages, and last 50 results with timestamps and reasoning - Automatic mode switching based on argument winner - Webhook management per channel with profile pictures and display names - Progressive probability system for dynamic argument lengths (starts at 10%, increases 5% per exchange, min 4 exchanges) - Draw handling with penalty system (-5% end chance, continues argument) - Integration with autonomous system for random argument triggers Argument System: - MIN_EXCHANGES = 4, progressive end chance starting at 10% - Enhanced prompts for both personas (strategic, short, punchy responses 1-3 sentences) - Evil Miku triumphant victory messages with gloating and satisfaction - Regular Miku assertive defense (not passive, shows backbone) - Message-based argument starting (can respond to specific messages via ID) - Conversation history tracking per argument with special user_id - Full context queries (personality, lore, lyrics, last 8 messages) LLM Arbiter: - Decisive prompt emphasizing picking winners (draws should be rare) - Improved parsing with first-line exact matching and fallback counting - Debug logging for decision transparency - Arbiter reasoning stored in scoreboard history for review - Uses neutral TEXT_MODEL (not evil) for unbiased judgment Web UI & API: - Bipolar mode toggle button (only visible when evil mode is on) - Channel ID + Message ID input fields for argument triggering - Scoreboard display with win percentages and recent history - Manual argument trigger endpoint with string-based IDs - GET /bipolar-mode/scoreboard endpoint for stats retrieval - Real-time active arguments tracking (refreshes every 5 seconds) Prompt Optimizations: - All argument prompts limited to 1-3 sentences for impact - Evil Miku system prompt with variable response length guidelines - Removed walls of text, emphasizing brevity and precision - "Sometimes the cruelest response is the shortest one" Evil Miku Updates: - Added height to lore (15.8m tall, 10x bigger than regular Miku) - Height added to prompt facts for size-based belittling - More strategic and calculating personality in arguments Integration: - Bipolar mode state restoration on bot startup - Bot skips processing messages during active arguments - Autonomous system checks for bipolar triggers after actions - Import fixes (apply_evil_mode_changes/revert_evil_mode_changes) Technical Details: - State persistence via JSON (bipolar_mode_state.json, bipolar_webhooks.json, bipolar_scoreboard.json) - Webhook caching per guild with fallback creation - Event loop management with asyncio.create_task - Rate limiting and argument conflict prevention - Globals integration (BIPOLAR_MODE, BIPOLAR_WEBHOOKS, BIPOLAR_ARGUMENT_IN_PROGRESS, MOOD_EMOJIS) Files Changed: - bot/bot.py: Added bipolar mode restoration and argument-in-progress checks - bot/globals.py: Added bipolar mode state variables and mood emoji mappings - bot/utils/bipolar_mode.py: Complete 1106-line implementation - bot/utils/autonomous.py: Added bipolar argument trigger checks - bot/utils/evil_mode.py: Updated system prompt, added height info to lore/prompt - bot/api.py: Added bipolar mode endpoints (trigger, toggle, scoreboard) - bot/static/index.html: Added bipolar controls section with scoreboard - bot/memory/: Various DM conversation updates - bot/evil_miku_lore.txt: Added height description - bot/evil_miku_prompt.txt: Added height to facts, updated personality guidelines
2026-01-06 13:57:59 +02:00
2025-12-07 17:15:09 +02:00
except Exception as e:
logger.error(f"Error executing autonomous action: {e}")
2025-12-07 17:15:09 +02:00
async def autonomous_reaction_tick_v2(guild_id: int):
"""
Scheduled check for reacting to older messages.
This runs less frequently (e.g., every 20 minutes) and picks from recent messages.
"""
# Ask the engine if Miku should react (scheduled check)
should_react = autonomous_engine.should_react_to_message(guild_id, message_age_seconds=600) # Check 10 min old msgs
if not should_react:
return
logger.debug(f"[V2] Scheduled reaction check triggered for server {guild_id}")
2025-12-07 17:15:09 +02:00
try:
from utils.autonomous_v1_legacy import miku_autonomous_reaction_for_server
# Don't pass force_message - let it pick a random recent message
await miku_autonomous_reaction_for_server(guild_id, force_message=None)
# Record action
autonomous_engine.record_action(guild_id)
except Exception as e:
logger.error(f"Error executing scheduled reaction: {e}")
2025-12-07 17:15:09 +02:00
def on_message_event(message):
"""
Hook for bot.py to call on every message.
Updates context without LLM calls.
ONLY processes messages from the configured autonomous channel.
"""
if not message.guild:
return # DMs don't use this system
guild_id = message.guild.id
# Get server config to check if this is the autonomous channel
server_config = server_manager.get_server_config(guild_id)
if not server_config:
return # No config for this server
# CRITICAL: Only process messages from the autonomous channel
if message.channel.id != server_config.autonomous_channel_id:
return # Ignore messages from other channels
# Track the message
autonomous_engine.track_message(guild_id, author_is_bot=message.author.bot)
# Check if we should act (async, non-blocking)
if not message.author.bot: # Only check for human messages
asyncio.create_task(_check_and_act(guild_id))
# Also check if we should react to this specific message
asyncio.create_task(_check_and_react(guild_id, message))
async def _check_and_react(guild_id: int, message):
"""
Check if Miku should react to a new message with an emoji.
Called for each new message in real-time.
"""
# Calculate message age
from datetime import datetime, timezone
message_age = (datetime.now(timezone.utc) - message.created_at).total_seconds()
# Ask engine if we should react
should_react = autonomous_engine.should_react_to_message(guild_id, message_age)
if should_react:
logger.info(f"[V2] Real-time reaction triggered for message from {message.author.display_name}")
2025-12-07 17:15:09 +02:00
from utils.autonomous_v1_legacy import miku_autonomous_reaction_for_server
await miku_autonomous_reaction_for_server(guild_id, force_message=message)
# Record action (reactions count as actions for cooldown purposes)
autonomous_engine.record_action(guild_id)
async def _check_and_act(guild_id: int):
"""
Internal function to check if action should be taken.
Called after each message, but engine makes smart decision.
IMPORTANT: Pass triggered_by_message=True so the engine knows to respond
to the message instead of saying something random/general.
"""
# Rate limiting check
now = time.time()
if guild_id in _last_action_execution:
time_since_last = now - _last_action_execution[guild_id]
if time_since_last < _MIN_ACTION_INTERVAL:
return
action_type = autonomous_engine.should_take_action(guild_id, triggered_by_message=True)
if action_type:
logger.info(f"[V2] Message triggered autonomous action: {action_type}")
2025-12-07 17:15:09 +02:00
# Execute the action directly (don't call autonomous_tick_v2 which would check again)
from utils.autonomous_v1_legacy import (
miku_say_something_general_for_server,
miku_engage_random_user_for_server,
share_miku_tweet_for_server,
miku_detect_and_join_conversation_for_server
)
from utils.profile_picture_manager import profile_picture_manager
try:
if action_type == "general":
await miku_say_something_general_for_server(guild_id)
elif action_type == "engage_user":
await miku_engage_random_user_for_server(guild_id)
elif action_type == "share_tweet":
await share_miku_tweet_for_server(guild_id)
elif action_type == "join_conversation":
await miku_detect_and_join_conversation_for_server(guild_id)
elif action_type == "change_profile_picture":
# Get current mood for this server
mood, _ = server_manager.get_server_mood(guild_id)
logger.info(f"[V2] Changing profile picture (mood: {mood})")
2025-12-07 17:15:09 +02:00
result = await profile_picture_manager.change_profile_picture(mood=mood, debug=True)
if result["success"]:
logger.info(f"Profile picture changed successfully!")
2025-12-07 17:15:09 +02:00
else:
logger.warning(f"Profile picture change failed: {result.get('error')}")
2025-12-07 17:15:09 +02:00
# Record that action was taken
autonomous_engine.record_action(guild_id)
# Update rate limiter
_last_action_execution[guild_id] = time.time()
Implement Bipolar Mode: Dual persona arguments with webhooks, LLM arbiter, and persistent scoreboard Major Features: - Complete Bipolar Mode system allowing Regular Miku and Evil Miku to coexist and argue via webhooks - LLM arbiter system using neutral model to judge argument winners with detailed reasoning - Persistent scoreboard tracking wins, percentages, and last 50 results with timestamps and reasoning - Automatic mode switching based on argument winner - Webhook management per channel with profile pictures and display names - Progressive probability system for dynamic argument lengths (starts at 10%, increases 5% per exchange, min 4 exchanges) - Draw handling with penalty system (-5% end chance, continues argument) - Integration with autonomous system for random argument triggers Argument System: - MIN_EXCHANGES = 4, progressive end chance starting at 10% - Enhanced prompts for both personas (strategic, short, punchy responses 1-3 sentences) - Evil Miku triumphant victory messages with gloating and satisfaction - Regular Miku assertive defense (not passive, shows backbone) - Message-based argument starting (can respond to specific messages via ID) - Conversation history tracking per argument with special user_id - Full context queries (personality, lore, lyrics, last 8 messages) LLM Arbiter: - Decisive prompt emphasizing picking winners (draws should be rare) - Improved parsing with first-line exact matching and fallback counting - Debug logging for decision transparency - Arbiter reasoning stored in scoreboard history for review - Uses neutral TEXT_MODEL (not evil) for unbiased judgment Web UI & API: - Bipolar mode toggle button (only visible when evil mode is on) - Channel ID + Message ID input fields for argument triggering - Scoreboard display with win percentages and recent history - Manual argument trigger endpoint with string-based IDs - GET /bipolar-mode/scoreboard endpoint for stats retrieval - Real-time active arguments tracking (refreshes every 5 seconds) Prompt Optimizations: - All argument prompts limited to 1-3 sentences for impact - Evil Miku system prompt with variable response length guidelines - Removed walls of text, emphasizing brevity and precision - "Sometimes the cruelest response is the shortest one" Evil Miku Updates: - Added height to lore (15.8m tall, 10x bigger than regular Miku) - Height added to prompt facts for size-based belittling - More strategic and calculating personality in arguments Integration: - Bipolar mode state restoration on bot startup - Bot skips processing messages during active arguments - Autonomous system checks for bipolar triggers after actions - Import fixes (apply_evil_mode_changes/revert_evil_mode_changes) Technical Details: - State persistence via JSON (bipolar_mode_state.json, bipolar_webhooks.json, bipolar_scoreboard.json) - Webhook caching per guild with fallback creation - Event loop management with asyncio.create_task - Rate limiting and argument conflict prevention - Globals integration (BIPOLAR_MODE, BIPOLAR_WEBHOOKS, BIPOLAR_ARGUMENT_IN_PROGRESS, MOOD_EMOJIS) Files Changed: - bot/bot.py: Added bipolar mode restoration and argument-in-progress checks - bot/globals.py: Added bipolar mode state variables and mood emoji mappings - bot/utils/bipolar_mode.py: Complete 1106-line implementation - bot/utils/autonomous.py: Added bipolar argument trigger checks - bot/utils/evil_mode.py: Updated system prompt, added height info to lore/prompt - bot/api.py: Added bipolar mode endpoints (trigger, toggle, scoreboard) - bot/static/index.html: Added bipolar controls section with scoreboard - bot/memory/: Various DM conversation updates - bot/evil_miku_lore.txt: Added height description - bot/evil_miku_prompt.txt: Added height to facts, updated personality guidelines
2026-01-06 13:57:59 +02:00
# Check for bipolar argument trigger (only if bipolar mode is active)
try:
from utils.bipolar_mode import maybe_trigger_argument, is_bipolar_mode
if is_bipolar_mode():
server_config = server_manager.servers.get(guild_id)
if server_config and server_config.autonomous_channel_id:
channel = globals.client.get_channel(server_config.autonomous_channel_id)
if channel:
await maybe_trigger_argument(channel, globals.client, "Triggered after message-based action")
except Exception as bipolar_err:
logger.warning(f"Bipolar check error: {bipolar_err}")
Implement Bipolar Mode: Dual persona arguments with webhooks, LLM arbiter, and persistent scoreboard Major Features: - Complete Bipolar Mode system allowing Regular Miku and Evil Miku to coexist and argue via webhooks - LLM arbiter system using neutral model to judge argument winners with detailed reasoning - Persistent scoreboard tracking wins, percentages, and last 50 results with timestamps and reasoning - Automatic mode switching based on argument winner - Webhook management per channel with profile pictures and display names - Progressive probability system for dynamic argument lengths (starts at 10%, increases 5% per exchange, min 4 exchanges) - Draw handling with penalty system (-5% end chance, continues argument) - Integration with autonomous system for random argument triggers Argument System: - MIN_EXCHANGES = 4, progressive end chance starting at 10% - Enhanced prompts for both personas (strategic, short, punchy responses 1-3 sentences) - Evil Miku triumphant victory messages with gloating and satisfaction - Regular Miku assertive defense (not passive, shows backbone) - Message-based argument starting (can respond to specific messages via ID) - Conversation history tracking per argument with special user_id - Full context queries (personality, lore, lyrics, last 8 messages) LLM Arbiter: - Decisive prompt emphasizing picking winners (draws should be rare) - Improved parsing with first-line exact matching and fallback counting - Debug logging for decision transparency - Arbiter reasoning stored in scoreboard history for review - Uses neutral TEXT_MODEL (not evil) for unbiased judgment Web UI & API: - Bipolar mode toggle button (only visible when evil mode is on) - Channel ID + Message ID input fields for argument triggering - Scoreboard display with win percentages and recent history - Manual argument trigger endpoint with string-based IDs - GET /bipolar-mode/scoreboard endpoint for stats retrieval - Real-time active arguments tracking (refreshes every 5 seconds) Prompt Optimizations: - All argument prompts limited to 1-3 sentences for impact - Evil Miku system prompt with variable response length guidelines - Removed walls of text, emphasizing brevity and precision - "Sometimes the cruelest response is the shortest one" Evil Miku Updates: - Added height to lore (15.8m tall, 10x bigger than regular Miku) - Height added to prompt facts for size-based belittling - More strategic and calculating personality in arguments Integration: - Bipolar mode state restoration on bot startup - Bot skips processing messages during active arguments - Autonomous system checks for bipolar triggers after actions - Import fixes (apply_evil_mode_changes/revert_evil_mode_changes) Technical Details: - State persistence via JSON (bipolar_mode_state.json, bipolar_webhooks.json, bipolar_scoreboard.json) - Webhook caching per guild with fallback creation - Event loop management with asyncio.create_task - Rate limiting and argument conflict prevention - Globals integration (BIPOLAR_MODE, BIPOLAR_WEBHOOKS, BIPOLAR_ARGUMENT_IN_PROGRESS, MOOD_EMOJIS) Files Changed: - bot/bot.py: Added bipolar mode restoration and argument-in-progress checks - bot/globals.py: Added bipolar mode state variables and mood emoji mappings - bot/utils/bipolar_mode.py: Complete 1106-line implementation - bot/utils/autonomous.py: Added bipolar argument trigger checks - bot/utils/evil_mode.py: Updated system prompt, added height info to lore/prompt - bot/api.py: Added bipolar mode endpoints (trigger, toggle, scoreboard) - bot/static/index.html: Added bipolar controls section with scoreboard - bot/memory/: Various DM conversation updates - bot/evil_miku_lore.txt: Added height description - bot/evil_miku_prompt.txt: Added height to facts, updated personality guidelines
2026-01-06 13:57:59 +02:00
2025-12-07 17:15:09 +02:00
except Exception as e:
logger.error(f"Error executing message-triggered action: {e}")
2025-12-07 17:15:09 +02:00
def on_presence_update(member, before, after):
"""
Hook for presence updates (status changes, activities).
Args:
member: The Member object (from 'after' in discord.py event)
before: Member object with old state
after: Member object with new state
"""
# Ignore bot users (including music bots that spam activity updates)
if member.bot:
return
guild_id = member.guild.id
# Track status changes
if before.status != after.status:
autonomous_engine.track_user_event(guild_id, "status_changed")
logger.debug(f"[V2] {member.display_name} status changed: {before.status}{after.status}")
2025-12-07 17:15:09 +02:00
# Track activity changes
if before.activities != after.activities:
# Check for new activities
before_activity_names = {a.name for a in before.activities if hasattr(a, 'name')}
after_activity_names = {a.name for a in after.activities if hasattr(a, 'name')}
new_activities = after_activity_names - before_activity_names
for activity_name in new_activities:
autonomous_engine.track_user_event(
guild_id,
"activity_started",
{"activity_name": activity_name}
)
logger.debug(f"[V2] {member.display_name} started activity: {activity_name}")
2025-12-07 17:15:09 +02:00
def on_member_join(member):
"""Hook for member join events"""
# Ignore bot users
if member.bot:
return
guild_id = member.guild.id
autonomous_engine.track_user_event(guild_id, "user_joined")
def on_mood_change(guild_id: int, new_mood: str):
"""Hook for mood changes"""
autonomous_engine.update_mood(guild_id, new_mood)
async def periodic_decay_task():
"""
Background task that decays event counters and saves context.
Run this every 15 minutes.
"""
task_start_time = time.time()
iteration_count = 0
while True:
await asyncio.sleep(900) # 15 minutes
iteration_count += 1
# Use list() to safely iterate even if dict changes
guild_ids = list(server_manager.servers.keys())
for guild_id in guild_ids:
try:
autonomous_engine.decay_events(guild_id)
except Exception as e:
logger.warning(f"Error decaying events for guild {guild_id}: {e}")
2025-12-07 17:15:09 +02:00
# Save context to disk periodically
try:
autonomous_engine.save_context()
except Exception as e:
logger.error(f"Error saving autonomous context: {e}")
2025-12-07 17:15:09 +02:00
uptime_hours = (time.time() - task_start_time) / 3600
logger.debug(f"[V2] Decay task completed (iteration #{iteration_count}, uptime: {uptime_hours:.1f}h)")
logger.debug(f" └─ Processed {len(guild_ids)} servers")
2025-12-07 17:15:09 +02:00
def initialize_v2_system(client):
"""
Initialize the V2 autonomous system.
Call this from bot.py on startup.
"""
logger.debug("Initializing Autonomous V2 System...")
2025-12-07 17:15:09 +02:00
# Initialize mood states for all servers
for guild_id, server_config in server_manager.servers.items():
autonomous_engine.update_mood(guild_id, server_config.current_mood_name)
# Start decay task
client.loop.create_task(periodic_decay_task())
logger.info("Autonomous V2 System initialized")
2025-12-07 17:15:09 +02:00
# ========== Legacy Function Wrappers ==========
# These maintain compatibility with old code that imports from autonomous.py
from utils.autonomous_v1_legacy import (
load_last_sent_tweets,
save_last_sent_tweets,
setup_autonomous_speaking,
# Server-specific functions
miku_autonomous_tick_for_server,
miku_say_something_general_for_server,
miku_engage_random_user_for_server,
miku_detect_and_join_conversation_for_server,
share_miku_tweet_for_server,
miku_autonomous_reaction_for_server,
miku_autonomous_reaction_for_dm,
handle_custom_prompt_for_server,
# Legacy global functions (for API compatibility)
miku_autonomous_tick,
miku_say_something_general,
miku_engage_random_user,
miku_detect_and_join_conversation,
share_miku_tweet,
handle_custom_prompt,
miku_autonomous_reaction,
)
# Alias the V2 tick as the main autonomous tick
autonomous_tick = autonomous_tick_v2
autonomous_reaction_tick = autonomous_reaction_tick_v2