feat: Coder를 에이전트 모드로 전환 + 리뷰 재시도 루프
핵심 변경: - gemini_caller.py: call_agent() 추가 (cwd 지원, 5분 타임아웃) Gemini가 프로젝트 디렉토리에서 직접 파일 읽기/쓰기/실행 - task_pipeline.py: Coder가 call_agent() 사용, file_applier 의존 제거 리뷰 실패 시 최대 2회 재시도 (피드백 포함) - discord_bot.py: pipeline.execute() 호출로 단순화 - coder.md: 파일 직접 쓰기 지시 (코드블록 출력 금지) - 검증: echo prompt | gemini --cwd=VW_Proj → test_agent.txt 생성 확인
This commit is contained in:
@@ -285,7 +285,7 @@ async def _handle_task(message: discord.Message, text: str, ws):
|
||||
description=f"```{text[:200]}```",
|
||||
color=0x3498DB,
|
||||
)
|
||||
embed.set_footer(text=f"ID: {task_id} — 워크스페이스: {ws.name}")
|
||||
embed.set_footer(text=f"ID: {task_id} | {ws.name}")
|
||||
status_msg = await message.channel.send(embed=embed)
|
||||
|
||||
try:
|
||||
@@ -297,102 +297,73 @@ async def _handle_task(message: discord.Message, text: str, ws):
|
||||
)
|
||||
pipeline.setup()
|
||||
|
||||
# Plan
|
||||
# 진행 상태 표시
|
||||
embed.color = 0xF39C12
|
||||
embed.set_footer(text=f"🔍 작업 분해 중... (ID: {task_id})")
|
||||
await status_msg.edit(embed=embed)
|
||||
|
||||
plan = await pipeline.plan(text)
|
||||
tasks = plan.get("tasks", [])
|
||||
# 전체 파이프라인 실행 (Plan -> Code(에이전트) -> Review(재시도) -> 총평)
|
||||
result = await pipeline.execute(text)
|
||||
|
||||
plan_embed = discord.Embed(
|
||||
title="📝 작업 계획",
|
||||
description=f"```{plan.get('summary', str(plan))[:500]}```",
|
||||
color=0x2ECC71,
|
||||
)
|
||||
plan = result.get("plan", {})
|
||||
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', '?'))}"
|
||||
for t in tasks[:10]
|
||||
)
|
||||
plan_embed = discord.Embed(
|
||||
title="📝 작업 계획",
|
||||
description=plan.get("summary", "")[:500],
|
||||
color=0x2ECC71,
|
||||
)
|
||||
plan_embed.add_field(
|
||||
name=f"태스크 ({len(tasks)}개)", value=task_list[:1000], inline=False,
|
||||
)
|
||||
await message.channel.send(embed=plan_embed)
|
||||
await message.channel.send(embed=plan_embed)
|
||||
|
||||
if not tasks:
|
||||
# 리뷰 결과
|
||||
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="⚠️ 실행할 태스크 없음",
|
||||
description="요청을 더 구체적으로 해주세요.",
|
||||
color=0xF39C12,
|
||||
title=f"{'✅' if passed else '⚠️'} 리뷰 결과{retry_info}",
|
||||
description=review.get("summary", str(review))[:500],
|
||||
color=0x2ECC71 if passed else 0xE74C3C,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
# Code 병렬
|
||||
code_embed = discord.Embed(
|
||||
title=f"⚙️ 코딩 중... ({len(tasks)}개 병렬)",
|
||||
description="\n".join(
|
||||
f"• {t.get('title', '?')[:60]}" for t in tasks
|
||||
),
|
||||
color=0xE67E22,
|
||||
)
|
||||
code_msg = await message.channel.send(embed=code_embed)
|
||||
code_outputs = await pipeline.code_parallel(tasks)
|
||||
code_embed.title = f"✅ 코딩 완료 ({len(tasks)}개)"
|
||||
code_embed.color = 0x2ECC71
|
||||
await code_msg.edit(embed=code_embed)
|
||||
|
||||
# 파일 적용
|
||||
from core.file_applier import parse_code_output, apply_changes
|
||||
all_applied = []
|
||||
for output in code_outputs:
|
||||
if not output.startswith("[ERROR]"):
|
||||
changes = parse_code_output(output)
|
||||
if changes:
|
||||
applied = apply_changes(changes, ws.path)
|
||||
all_applied.extend(applied)
|
||||
|
||||
if all_applied:
|
||||
files_text = "\n".join(f"• `{f['path']}` ({f['action']})" for f in all_applied[:15])
|
||||
await message.channel.send(
|
||||
embed=discord.Embed(title=f"📁 파일 적용 ({len(all_applied)}개)",
|
||||
description=files_text, color=0x3498DB)
|
||||
)
|
||||
|
||||
# Batch Review
|
||||
review = await pipeline.batch_review(tasks, code_outputs)
|
||||
passed = review.get("passed", True)
|
||||
await message.channel.send(
|
||||
embed=discord.Embed(
|
||||
title=f"{'✅' if passed else '⚠️'} 리뷰 결과",
|
||||
description=review.get("summary", str(review))[:500],
|
||||
color=0x2ECC71 if passed else 0xE74C3C,
|
||||
)
|
||||
)
|
||||
|
||||
# 총평
|
||||
summary = await pipeline.summarize(text, plan, code_outputs, review, all_applied)
|
||||
|
||||
summary_embed = discord.Embed(
|
||||
title=f"📊 {summary.get('title', '작업 완료')}",
|
||||
description=summary.get("summary", "완료"),
|
||||
color=0x9B59B6,
|
||||
)
|
||||
for field_name, key, emoji in [
|
||||
("변경 사항", "changes", ""),
|
||||
("⚠️ 주의", "warnings", ""),
|
||||
("🔜 다음 단계", "next_steps", ""),
|
||||
for field_name, key in [
|
||||
("변경 사항", "changes"),
|
||||
("⚠️ 주의", "warnings"),
|
||||
("🔜 다음 단계", "next_steps"),
|
||||
]:
|
||||
items = summary.get(key, [])
|
||||
if items:
|
||||
if key == "changes":
|
||||
val = "\n".join(f"• `{c.get('file','?')}` — {c.get('description','')}" for c in items[:10])
|
||||
if key == "changes" and isinstance(items[0], dict):
|
||||
val = "\n".join(
|
||||
f"• `{c.get('file','?')}` - {c.get('description','')}"
|
||||
for c in items[:10]
|
||||
)
|
||||
else:
|
||||
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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user