Connect orchestrator to Twitch chat

This commit is contained in:
2026-05-12 10:44:58 -05:00
parent 9bc6a7a24e
commit c2d1c176df
4 changed files with 365 additions and 20 deletions

View File

@@ -14,6 +14,7 @@ from app.memory.database import init_db
from app.memory.database import get_session as get_db_session
from app.memory.repository import Repository
from app.exports.markdown import MarkdownExporter
from app.twitch.chat import TwitchChatMessage, TwitchIRCClient, set_active_client
logger = logging.getLogger(__name__)
@@ -40,6 +41,9 @@ class DashboardRequest(BaseModel):
# Global orchestrator instance
orchestrator: AgentOrchestrator | None = None
agent_loop_task: asyncio.Task | None = None
twitch_chat_client: TwitchIRCClient | None = None
twitch_chat_task: asyncio.Task | None = None
twitch_session_id: str | None = None
async def require_admin(
@@ -73,6 +77,78 @@ async def agent_loop() -> None:
await asyncio.sleep(orchestrator.loop_interval_seconds)
def twitch_configured() -> bool:
"""Return whether Twitch chat has enough runtime configuration to start."""
return bool(
settings.TWITCH_CHAT_ENABLED
and settings.TWITCH_CHANNEL_NAME
and settings.TWITCH_BOT_USERNAME
and settings.TWITCH_ACCESS_TOKEN
)
async def get_or_create_twitch_session(channel_name: str) -> str:
"""Use an active session for the Twitch channel, or create one."""
if not orchestrator:
raise RuntimeError("Orchestrator not initialized")
normalized_channel = channel_name.lower()
matching_sessions = [
(session_id, session)
for session_id, session in orchestrator.active_sessions.items()
if session.get("channel_name", "").lower() == normalized_channel
]
if matching_sessions:
session_id, _ = max(
matching_sessions,
key=lambda item: item[1].get("started_at", datetime.min),
)
return session_id
return await orchestrator.start_session(channel_name)
async def handle_twitch_chat_message(message: TwitchChatMessage) -> None:
"""Route a Twitch chat message into the orchestrator."""
if not orchestrator or not twitch_session_id:
return
if settings.TWITCH_BOT_USERNAME and (
message.username.lower() == settings.TWITCH_BOT_USERNAME.lower()
):
return
await orchestrator.handle_chat_message(
session_id=twitch_session_id,
username=message.display_name or message.username,
message=message.content,
)
async def start_twitch_chat() -> None:
"""Start Twitch chat monitoring when configured."""
global twitch_chat_client, twitch_chat_task, twitch_session_id
if not orchestrator or not twitch_configured():
logger.info("Twitch chat listener not started; configuration is incomplete")
return
twitch_session_id = await get_or_create_twitch_session(settings.TWITCH_CHANNEL_NAME)
twitch_chat_client = TwitchIRCClient(
channel_name=settings.TWITCH_CHANNEL_NAME,
bot_username=settings.TWITCH_BOT_USERNAME,
access_token=settings.TWITCH_ACCESS_TOKEN,
on_message=handle_twitch_chat_message,
)
set_active_client(twitch_chat_client)
twitch_chat_task = asyncio.create_task(twitch_chat_client.run())
logger.info(
"Twitch chat listener starting for %s on session %s",
settings.TWITCH_CHANNEL_NAME,
twitch_session_id,
)
@app.on_event("startup")
async def startup_event():
"""Initialize database and services on startup."""
@@ -84,6 +160,7 @@ async def startup_event():
)
await orchestrator.restore_active_sessions()
agent_loop_task = asyncio.create_task(agent_loop())
await start_twitch_chat()
logger.info("Application started successfully")
except Exception as e:
logger.error(f"Failed to start application: {e}")
@@ -93,6 +170,16 @@ async def startup_event():
@app.on_event("shutdown")
async def shutdown_event():
"""Clean up resources on shutdown."""
global twitch_chat_client
if twitch_chat_task:
twitch_chat_task.cancel()
with suppress(asyncio.CancelledError):
await twitch_chat_task
if twitch_chat_client:
await twitch_chat_client.disconnect()
set_active_client(None)
twitch_chat_client = None
if agent_loop_task:
agent_loop_task.cancel()
with suppress(asyncio.CancelledError):
@@ -308,6 +395,20 @@ async def get_loop_status() -> dict:
}
@app.get("/admin/twitch/status", dependencies=[Depends(require_admin)])
async def get_twitch_status() -> dict:
"""Get Twitch chat connection status."""
configured = twitch_configured()
client_status = twitch_chat_client.status() if twitch_chat_client else {}
return {
"configured": configured,
"running": bool(twitch_chat_task and not twitch_chat_task.done()),
"session_id": twitch_session_id,
**client_status,
"timestamp": datetime.utcnow().isoformat(),
}
@app.post("/admin/loop/frequency", dependencies=[Depends(require_admin)])
async def set_loop_frequency(interval_seconds: float = Form(...)) -> dict:
"""Set how frequently the background agent loop runs."""