From a54a9096efbb6d103a772dc5370510ea8df42f70 Mon Sep 17 00:00:00 2001 From: CD Date: Fri, 6 Mar 2026 22:22:41 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=8B=A8=EA=B3=84=EB=B3=84=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=ED=91=9C=EC=8B=9C=20+=20em-dash=20=EC=9D=B8?= =?UTF-8?q?=EC=BD=94=EB=94=A9=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _handle_task: pipeline.execute() 대신 단계별 호출로 변경 -> 계획/코딩/리뷰/재시도 각 단계를 Discord에 실시간 표시 -> 코딩 중.../리뷰 중... Embed가 완료 시 업데이트됨 - em-dash -> ASCII dash (cp949 인코딩 오류 방지) --- api/discord_bot.py | 102 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 25 deletions(-) diff --git a/api/discord_bot.py b/api/discord_bot.py index dc3e9b6..f6e4834 100644 --- a/api/discord_bot.py +++ b/api/discord_bot.py @@ -213,7 +213,7 @@ async def on_message(message: discord.Message): return mode = result.get("mode", "chat") - logger.info(f"통합 분류: {mode} — \"{user_text[:50]}\"") + logger.info(f"통합 분류: {mode} - \"{user_text[:50]}\"") if mode == "task": # Git/Vikunja 미설정 안내 (차단하지 않음) @@ -275,7 +275,7 @@ async def _send_setup_warning(message: discord.Message, ws): # ────────────────────────────────────────────── async def _handle_task(message: discord.Message, text: str, ws): - """작업 요청 — 파이프라인 실행.""" + """작업 요청 — 파이프라인 단계별 실행 + 진행 표시.""" import uuid task_id = uuid.uuid4().hex[:8] @@ -289,7 +289,7 @@ async def _handle_task(message: discord.Message, text: str, ws): status_msg = await message.channel.send(embed=embed) try: - from core.task_pipeline import TaskPipeline + from core.task_pipeline import TaskPipeline, MAX_REVIEW_RETRIES pipeline = TaskPipeline( project_path=ws.path, @@ -297,20 +297,14 @@ async def _handle_task(message: discord.Message, text: str, ws): ) pipeline.setup() - # 진행 상태 표시 + # 1. Plan + embed.title = "🔍 작업 분해 중..." embed.color = 0xF39C12 - embed.set_footer(text=f"🔍 작업 분해 중... (ID: {task_id})") await status_msg.edit(embed=embed) - # 전체 파이프라인 실행 (Plan -> Code(에이전트) -> Review(재시도) -> 총평) - result = await pipeline.execute(text) - - plan = result.get("plan", {}) + plan = await pipeline.plan(text) tasks = plan.get("tasks", []) - review = result.get("review", {}) - summary = result.get("summary", {}) - # 계획 표시 if tasks: task_list = "\n".join( f"• {t.get('title', t.get('description', '?'))}" @@ -325,23 +319,78 @@ async def _handle_task(message: discord.Message, text: str, ws): name=f"태스크 ({len(tasks)}개)", value=task_list[:1000], inline=False, ) await message.channel.send(embed=plan_embed) + else: + await message.channel.send( + embed=discord.Embed( + title="⚠️ 실행할 태스크 없음", + description="요청을 더 구체적으로 해주세요.", + color=0xF39C12, + ) + ) + return + + # 2. Code + Review (재시도 루프) + review = None + code_outputs = [] + for attempt in range(1 + MAX_REVIEW_RETRIES): + attempt_label = f" (재시도 {attempt})" if attempt > 0 else "" + + # 코딩 진행 표시 + code_embed = discord.Embed( + title=f"⚙️ 코딩 중...{attempt_label} ({len(tasks)}개 에이전트)", + description="\n".join( + f"• {t.get('title', '?')[:60]}" for t in tasks[:10] + ), + color=0xE67E22, + ) + code_msg = await message.channel.send(embed=code_embed) + + code_outputs = await pipeline.code_parallel(tasks) + + error_count = sum(1 for o in code_outputs if o.startswith("[ERROR]")) + code_embed.title = f"✅ 코딩 완료{attempt_label} ({len(tasks) - error_count}/{len(tasks)} 성공)" + code_embed.color = 0x2ECC71 if error_count == 0 else 0xF39C12 + await code_msg.edit(embed=code_embed) + + # 리뷰 + review_embed = discord.Embed( + title="🔍 리뷰 중...", + color=0xF39C12, + ) + review_msg = await message.channel.send(embed=review_embed) + + review = await pipeline.batch_review(tasks, code_outputs) - # 리뷰 결과 - if review: passed = review.get("passed", True) if isinstance(passed, str): passed = passed.lower() in ("true", "yes", "pass") - retry_count = result.get("retry_count", 0) - retry_info = f" (재시도 {retry_count}회)" if retry_count > 0 else "" - await message.channel.send( - embed=discord.Embed( - title=f"{'✅' if passed else '⚠️'} 리뷰 결과{retry_info}", - description=review.get("summary", str(review))[:500], - color=0x2ECC71 if passed else 0xE74C3C, - ) - ) - # 총평 + review_embed.title = f"{'✅' if passed else '⚠️'} 리뷰 결과{attempt_label}" + review_embed.description = review.get("summary", str(review))[:500] + review_embed.color = 0x2ECC71 if passed else 0xE74C3C + await review_msg.edit(embed=review_embed) + + if passed: + break + elif attempt < MAX_REVIEW_RETRIES: + # 재시도 안내 + await message.channel.send( + embed=discord.Embed( + title=f"🔄 재시도 {attempt+1}/{MAX_REVIEW_RETRIES}", + description="리뷰 피드백을 반영하여 다시 코딩합니다.", + color=0xF39C12, + ) + ) + feedback = review.get("summary", str(review))[:500] + for task in tasks: + task["review_feedback"] = ( + f"이전 시도에서 다음 리뷰 피드백을 받았습니다. " + f"반드시 수정하세요:\n{feedback}" + ) + + # 3. 총평 + summary = await pipeline.summarize(text, plan, code_outputs, review) + summary_embed = discord.Embed( title=f"📊 {summary.get('title', '작업 완료')}", description=summary.get("summary", "완료"), @@ -363,10 +412,13 @@ async def _handle_task(message: discord.Message, text: str, ws): val = "\n".join(f"• {s}" for s in items) summary_embed.add_field(name=field_name, value=val[:1000], inline=False) - summary_embed.set_footer(text=f"ID: {task_id} | {ws.name}") await message.channel.send(embed=summary_embed) + # 기록 + pipeline.docs.record_session(text, summary, plan) + pipeline.docs.append_changelog(summary.get("title", text[:50])) + except GeminiCallError as e: await message.channel.send( embed=discord.Embed(title="❌ AI 호출 오류",