#!/usr/bin/env python3 """ Discord Fishbowl - Autonomous AI Character Chat System Main entry point for the application """ import asyncio import signal import sys import os from pathlib import Path # Add src to Python path sys.path.insert(0, str(Path(__file__).parent)) from utils.config import get_settings, validate_environment, setup_logging from utils.logging import setup_logging_interceptor from database.connection import init_database, create_tables, close_database from bot.discord_client import FishbowlBot from bot.message_handler import MessageHandler, CommandHandler from conversation.engine import ConversationEngine from conversation.scheduler import ConversationScheduler from llm.multi_provider_client import multi_llm_client, initialize_llm_client from rag.vector_store import vector_store_manager from rag.community_knowledge import initialize_community_knowledge_rag from rag.memory_sharing import MemorySharingManager from collaboration.creative_projects import CollaborativeCreativeManager from mcp_servers.self_modification_server import mcp_server from mcp_servers.file_system_server import filesystem_server from mcp_servers.calendar_server import calendar_server from mcp_servers.memory_sharing_server import MemorySharingMCPServer from mcp_servers.creative_projects_server import initialize_creative_projects_mcp_server import logging # Setup logging first logger = setup_logging() setup_logging_interceptor() class FishbowlApplication: """Main application class""" def __init__(self): self.settings = None self.conversation_engine = None self.scheduler = None self.discord_bot = None self.message_handler = None self.command_handler = None self.shutdown_event = asyncio.Event() # RAG and MCP systems self.vector_store = None self.community_knowledge = None self.memory_sharing_manager = None self.creative_manager = None self.mcp_servers = [] async def initialize(self): """Initialize all components""" try: logger.info("Starting Discord Fishbowl initialization...") # Validate environment validate_environment() # Load settings self.settings = get_settings() logger.info("Configuration loaded successfully") # Initialize database await init_database() await create_tables() logger.info("Database initialized") # Initialize multi-provider LLM client logger.info("Initializing multi-provider LLM system...") await initialize_llm_client() # Check provider health (non-blocking) health_status = await multi_llm_client.health_check() provider_info = multi_llm_client.get_provider_info() healthy_providers = [name for name, healthy in health_status.items() if healthy] if healthy_providers: current_provider = multi_llm_client.get_current_provider() logger.info(f"LLM providers available: {healthy_providers}") logger.info(f"Current primary provider: {current_provider}") else: logger.warning("No LLM providers are healthy! Bot will continue and retry connections.") # Initialize RAG systems logger.info("Initializing RAG systems...") # Initialize vector store self.vector_store = vector_store_manager character_names = ["Alex", "Sage", "Luna", "Echo"] # From config await self.vector_store.initialize(character_names) logger.info("Vector store initialized") # Initialize community knowledge RAG self.community_knowledge = initialize_community_knowledge_rag(self.vector_store) await self.community_knowledge.initialize(character_names) logger.info("Community knowledge RAG initialized") # Initialize memory sharing manager self.memory_sharing_manager = MemorySharingManager(self.vector_store) await self.memory_sharing_manager.initialize(character_names) logger.info("Memory sharing manager initialized") # Initialize collaborative creative manager self.creative_manager = CollaborativeCreativeManager(self.vector_store, self.memory_sharing_manager) await self.creative_manager.initialize(character_names) logger.info("Collaborative creative manager initialized") # Initialize MCP servers logger.info("Initializing MCP servers...") # Initialize self-modification server self.mcp_servers.append(mcp_server) logger.info("Self-modification MCP server initialized") # Initialize file system server await filesystem_server.initialize(self.vector_store, character_names) self.mcp_servers.append(filesystem_server) logger.info("File system MCP server initialized") # Initialize calendar/time awareness server await calendar_server.initialize(character_names) self.mcp_servers.append(calendar_server) logger.info("Calendar/time awareness MCP server initialized") # Initialize memory sharing MCP server memory_sharing_mcp = MemorySharingMCPServer(self.memory_sharing_manager) self.mcp_servers.append(memory_sharing_mcp) logger.info("Memory sharing MCP server initialized") # Initialize creative projects MCP server creative_projects_mcp = initialize_creative_projects_mcp_server(self.creative_manager) self.mcp_servers.append(creative_projects_mcp) logger.info("Creative projects MCP server initialized") # Initialize conversation engine with RAG and MCP systems self.conversation_engine = ConversationEngine( vector_store=self.vector_store, memory_sharing_manager=self.memory_sharing_manager, creative_manager=self.creative_manager, mcp_servers=self.mcp_servers ) logger.info("Conversation engine created with enhanced capabilities") # Initialize scheduler self.scheduler = ConversationScheduler(self.conversation_engine) logger.info("Conversation scheduler created") # Initialize Discord bot self.discord_bot = FishbowlBot(self.conversation_engine) # Set global bot instance for status messages import bot.discord_client bot.discord_client._discord_bot = self.discord_bot # Initialize message and command handlers self.message_handler = MessageHandler(self.discord_bot, self.conversation_engine) self.command_handler = CommandHandler(self.discord_bot, self.conversation_engine) logger.info("Discord bot and handlers initialized") logger.info("✅ All components initialized successfully") except Exception as e: logger.error(f"Failed to initialize application: {e}") raise async def start(self): """Start the application""" try: logger.info("🚀 Starting Discord Fishbowl...") # Start conversation engine await self.conversation_engine.initialize(self.discord_bot) logger.info("Conversation engine started") # Start scheduler await self.scheduler.start() logger.info("Conversation scheduler started") # Start LLM cleanup task cleanup_task = asyncio.create_task(self._llm_cleanup_loop()) logger.info("LLM cleanup task started") # Start Discord bot bot_task = asyncio.create_task( self.discord_bot.start(self.settings.discord.token) ) # Setup signal handlers self._setup_signal_handlers() logger.info("🎉 Discord Fishbowl is now running!") logger.info("Characters will start chatting autonomously...") # Wait for shutdown signal or bot completion done, pending = await asyncio.wait( [bot_task, cleanup_task, asyncio.create_task(self.shutdown_event.wait())], return_when=asyncio.FIRST_COMPLETED ) # Cancel pending tasks for task in pending: task.cancel() try: await task except asyncio.CancelledError: pass except Exception as e: logger.error(f"Error during application startup: {e}") raise async def shutdown(self): """Graceful shutdown""" try: logger.info("🛑 Shutting down Discord Fishbowl...") # Stop scheduler if self.scheduler: await self.scheduler.stop() logger.info("Conversation scheduler stopped") # Stop conversation engine if self.conversation_engine: await self.conversation_engine.stop() logger.info("Conversation engine stopped") # Close Discord bot if self.discord_bot: await self.discord_bot.close() logger.info("Discord bot disconnected") # Close database connections await close_database() logger.info("Database connections closed") logger.info("✅ Shutdown completed successfully") except Exception as e: logger.error(f"Error during shutdown: {e}") def _setup_signal_handlers(self): """Setup signal handlers for graceful shutdown""" def signal_handler(signum, frame): logger.info(f"Received signal {signum}, initiating shutdown...") self.shutdown_event.set() # Handle common shutdown signals signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) # On Windows, handle CTRL+C if os.name == 'nt': signal.signal(signal.SIGBREAK, signal_handler) async def _llm_cleanup_loop(self): """Background task to monitor LLM provider health""" try: while not self.shutdown_event.is_set(): # Check provider health periodically health_status = await multi_llm_client.health_check() unhealthy_providers = [name for name, healthy in health_status.items() if not healthy] if unhealthy_providers: logger.debug(f"Unhealthy LLM providers: {unhealthy_providers}") # Wait 60 seconds before next health check await asyncio.sleep(60) except asyncio.CancelledError: logger.info("LLM monitoring task cancelled") except Exception as e: logger.error(f"Error in LLM cleanup loop: {e}") async def main(): """Main entry point""" app = FishbowlApplication() try: # Initialize application await app.initialize() # Start application await app.start() except KeyboardInterrupt: logger.info("Received keyboard interrupt") except Exception as e: logger.error(f"Application error: {e}") return 1 finally: # Ensure cleanup await app.shutdown() return 0 def cli_main(): """CLI entry point""" try: # Check Python version if sys.version_info < (3, 8): print("Error: Python 3.8 or higher is required") return 1 # Run the async main function return asyncio.run(main()) except KeyboardInterrupt: print("\nApplication interrupted by user") return 1 except Exception as e: print(f"Fatal error: {e}") return 1 if __name__ == "__main__": sys.exit(cli_main())