fix: 단계별 진행 표시 + em-dash 인코딩 수정

- _handle_task: pipeline.execute() 대신 단계별 호출로 변경
  -> 계획/코딩/리뷰/재시도 각 단계를 Discord에 실시간 표시
  -> 코딩 중.../리뷰 중... Embed가 완료 시 업데이트됨
- em-dash -> ASCII dash (cp949 인코딩 오류 방지)
This commit is contained in:
2026-03-06 22:22:41 +09:00
parent bccc673713
commit a54a9096ef

View File

@@ -213,7 +213,7 @@ async def on_message(message: discord.Message):
return return
mode = result.get("mode", "chat") mode = result.get("mode", "chat")
logger.info(f"통합 분류: {mode} \"{user_text[:50]}\"") logger.info(f"통합 분류: {mode} - \"{user_text[:50]}\"")
if mode == "task": if mode == "task":
# Git/Vikunja 미설정 안내 (차단하지 않음) # 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): async def _handle_task(message: discord.Message, text: str, ws):
"""작업 요청 — 파이프라인 실행.""" """작업 요청 — 파이프라인 단계별 실행 + 진행 표시."""
import uuid import uuid
task_id = uuid.uuid4().hex[:8] 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) status_msg = await message.channel.send(embed=embed)
try: try:
from core.task_pipeline import TaskPipeline from core.task_pipeline import TaskPipeline, MAX_REVIEW_RETRIES
pipeline = TaskPipeline( pipeline = TaskPipeline(
project_path=ws.path, project_path=ws.path,
@@ -297,20 +297,14 @@ async def _handle_task(message: discord.Message, text: str, ws):
) )
pipeline.setup() pipeline.setup()
# 진행 상태 표시 # 1. Plan
embed.title = "🔍 작업 분해 중..."
embed.color = 0xF39C12 embed.color = 0xF39C12
embed.set_footer(text=f"🔍 작업 분해 중... (ID: {task_id})")
await status_msg.edit(embed=embed) await status_msg.edit(embed=embed)
# 전체 파이프라인 실행 (Plan -> Code(에이전트) -> Review(재시도) -> 총평) plan = await pipeline.plan(text)
result = await pipeline.execute(text)
plan = result.get("plan", {})
tasks = plan.get("tasks", []) tasks = plan.get("tasks", [])
review = result.get("review", {})
summary = result.get("summary", {})
# 계획 표시
if tasks: if tasks:
task_list = "\n".join( task_list = "\n".join(
f"{t.get('title', t.get('description', '?'))}" 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, name=f"태스크 ({len(tasks)}개)", value=task_list[:1000], inline=False,
) )
await message.channel.send(embed=plan_embed) 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) passed = review.get("passed", True)
if isinstance(passed, str): if isinstance(passed, str):
passed = passed.lower() in ("true", "yes", "pass") 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( summary_embed = discord.Embed(
title=f"📊 {summary.get('title', '작업 완료')}", title=f"📊 {summary.get('title', '작업 완료')}",
description=summary.get("summary", "완료"), 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) val = "\n".join(f"{s}" for s in items)
summary_embed.add_field(name=field_name, value=val[:1000], inline=False) summary_embed.add_field(name=field_name, value=val[:1000], inline=False)
summary_embed.set_footer(text=f"ID: {task_id} | {ws.name}") summary_embed.set_footer(text=f"ID: {task_id} | {ws.name}")
await message.channel.send(embed=summary_embed) 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: except GeminiCallError as e:
await message.channel.send( await message.channel.send(
embed=discord.Embed(title="❌ AI 호출 오류", embed=discord.Embed(title="❌ AI 호출 오류",