182 lines
5.7 KiB
Python
182 lines
5.7 KiB
Python
"""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()
|