"""Discord Bot 어댑터. 사용자 명령을 받아 FastAPI API를 호출하고 결과를 디스코드로 보고합니다. """ import asyncio import logging import discord from discord.ext import commands import config logger = logging.getLogger("variet.discord") # Bot 설정 intents = discord.Intents.default() intents.message_content = True # MESSAGE CONTENT INTENT 필요 bot = commands.Bot( command_prefix=config.DISCORD_COMMAND_PREFIX, intents=intents, help_command=commands.DefaultHelpCommand(no_category="명령어"), ) # In-memory: Discord 채널 ↔ Task 매핑 _channel_tasks: dict[int, list[str]] = {} @bot.event async def on_ready(): """봇 접속 완료.""" logger.info(f"Discord Bot 접속 완료: {bot.user} (ID: {bot.user.id})") logger.info(f"서버 {len(bot.guilds)}개 연결됨") await bot.change_presence( activity=discord.Activity( type=discord.ActivityType.listening, name=f"{config.DISCORD_COMMAND_PREFIX}agent", ) ) @bot.command(name="agent", help="AI Agent에게 작업 요청\n예: !agent README에 설치 방법 추가해줘") async def agent_command(ctx: commands.Context, *, request: str): """작업 요청 → Pipeline 실행 → 결과 보고.""" # 1. 접수 메시지 embed = discord.Embed( title="📋 작업 접수", description=f"```{request[:200]}```", color=0x3498DB, ) embed.set_footer(text="분석 중...") status_msg = await ctx.send(embed=embed) try: # 2. Pipeline 직접 실행 (같은 프로세스) from core.task_pipeline import TaskPipeline import uuid task_id = uuid.uuid4().hex[:8] # Planning embed.color = 0xF39C12 embed.set_footer(text=f"🔍 작업 분해 중... (ID: {task_id})") await status_msg.edit(embed=embed) pipeline = TaskPipeline( project_path=str(config.PROJECT_ROOT), ) pipeline.setup() # Plan 단계 plan = await pipeline.plan(request) tasks = plan.get("tasks", []) plan_text = plan.get("summary", str(plan))[:500] plan_embed = discord.Embed( title="📝 작업 계획", description=f"```{plan_text}```", color=0x2ECC71, ) if tasks: task_list = "\n".join( f"• {t.get('title', t.get('description', '?'))}" for t in tasks[:10] ) plan_embed.add_field( name=f"태스크 ({len(tasks)}개)", value=task_list[:1000], inline=False, ) plan_embed.set_footer(text=f"ID: {task_id}") await ctx.send(embed=plan_embed) # Code + Review 단계 if tasks: for i, task in enumerate(tasks, 1): progress_embed = discord.Embed( title=f"⚙️ 실행 중 ({i}/{len(tasks)})", description=task.get("title", task.get("description", ""))[:200], color=0xE67E22, ) await ctx.send(embed=progress_embed) code_output = await pipeline.code(task) review = await pipeline.review(task, code_output) passed = review.get("passed", True) review_emoji = "✅" if passed else "⚠️" review_embed = discord.Embed( title=f"{review_emoji} 리뷰 결과 ({i}/{len(tasks)})", description=review.get("summary", str(review))[:500], color=0x2ECC71 if passed else 0xE74C3C, ) await ctx.send(embed=review_embed) # 완료 done_embed = discord.Embed( title="✅ 작업 완료", description=f"총 {len(tasks)}개 태스크 처리 완료", color=0x2ECC71, ) done_embed.set_footer(text=f"ID: {task_id}") await ctx.send(embed=done_embed) # 채널 태스크 기록 _channel_tasks.setdefault(ctx.channel.id, []).append(task_id) except Exception as e: logger.error(f"작업 실행 오류: {e}", exc_info=True) error_embed = discord.Embed( title="❌ 오류 발생", description=f"```{str(e)[:500]}```", color=0xE74C3C, ) await ctx.send(embed=error_embed) @bot.command(name="ping", help="봇 응답 테스트") async def ping_command(ctx: commands.Context): """연결 상태 확인.""" latency = round(bot.latency * 1000) await ctx.send(f"🏓 Pong! ({latency}ms)") @bot.command(name="info", help="시스템 정보") async def info_command(ctx: commands.Context): """시스템 정보 표시.""" embed = discord.Embed( title="🤖 Variet Agent", description="AI Agent Team — Gemini CLI 기반 자동화 개발 에이전트", color=0x9B59B6, ) embed.add_field(name="프로젝트", value=str(config.PROJECT_ROOT), inline=False) embed.add_field(name="명령어 접두사", value=config.DISCORD_COMMAND_PREFIX, inline=True) embed.add_field(name="서버 수", value=str(len(bot.guilds)), inline=True) await ctx.send(embed=embed) async def start_bot(): """Discord Bot 시작 (async).""" token = config.DISCORD_BOT_TOKEN if not token: logger.error("DISCORD_BOT_TOKEN이 설정되지 않았습니다. .env 파일을 확인하세요.") return logger.info("Discord Bot 시작 중...") try: await bot.start(token) except discord.LoginFailure: logger.error("Discord 로그인 실패 — 토큰을 확인하세요.") except Exception as e: logger.error(f"Discord Bot 오류: {e}") async def stop_bot(): """Discord Bot 정지.""" if not bot.is_closed(): await bot.close()