181 lines
5.8 KiB
Python
181 lines
5.8 KiB
Python
"""태스크 핸들러 — Agent 1회 실행 (discord_bot.py에서 분리).
|
|
|
|
NLU에서 mode="task"로 분류된 요청을 처리합니다.
|
|
Agent가 plan+code+verify를 한 세션에서 수행합니다.
|
|
"""
|
|
|
|
import uuid
|
|
import logging
|
|
import asyncio
|
|
|
|
import discord
|
|
|
|
from core.gemini_caller import GeminiCallError
|
|
from handlers.renderer import safe_send_embed
|
|
|
|
logger = logging.getLogger("variet.handlers.task")
|
|
|
|
|
|
async def handle_task(
|
|
message: discord.Message,
|
|
text: str,
|
|
ws,
|
|
history: str = "",
|
|
mode: str = "task",
|
|
):
|
|
"""작업 요청 — Agent 1회 통합 실행 + 결과 표시.
|
|
|
|
Args:
|
|
message: Discord 메시지
|
|
text: 사용자 입력 텍스트
|
|
ws: Workspace 객체
|
|
history: 대화 히스토리 문자열
|
|
mode: 'task'(코딩) 또는 'anime'(도구 실행)
|
|
"""
|
|
from core.task_pipeline import TaskPipeline
|
|
|
|
task_id = uuid.uuid4().hex[:8]
|
|
|
|
# ── 1. 접수 ──
|
|
embed = discord.Embed(
|
|
title="⚙️ 작업 중...",
|
|
description=f"```{text[:200]}```",
|
|
color=0xF39C12,
|
|
)
|
|
embed.set_footer(text=f"ID: {task_id} | {ws.name}")
|
|
status_msg = await message.channel.send(embed=embed)
|
|
|
|
try:
|
|
pipeline = TaskPipeline(
|
|
project_path=ws.path,
|
|
docs_subpath=ws.docs_path,
|
|
)
|
|
pipeline.setup()
|
|
|
|
# ── 2. Agent 실행 (진행 상태 실시간 업데이트) ──
|
|
import time as _time
|
|
_start_time = _time.time()
|
|
_last_update = [0.0]
|
|
_current_status = ["작업 중..."]
|
|
|
|
async def _progress(status_text: str):
|
|
now = _time.time()
|
|
if now - _last_update[0] < 2.0:
|
|
return
|
|
_last_update[0] = now
|
|
_current_status[0] = status_text
|
|
elapsed = int(now - _start_time)
|
|
try:
|
|
embed.title = f"⚙️ {status_text}"
|
|
embed.set_footer(text=f"ID: {task_id} | {ws.name} | {elapsed}초 경과")
|
|
await status_msg.edit(embed=embed)
|
|
except Exception:
|
|
pass
|
|
|
|
# heartbeat: 출력이 없어도 10초마다 경과시간 갱신
|
|
_heartbeat_running = [True]
|
|
|
|
async def _heartbeat():
|
|
while _heartbeat_running[0]:
|
|
await asyncio.sleep(10)
|
|
if not _heartbeat_running[0]:
|
|
break
|
|
elapsed = int(_time.time() - _start_time)
|
|
try:
|
|
embed.set_footer(text=f"ID: {task_id} | {ws.name} | {elapsed}초 경과")
|
|
await status_msg.edit(embed=embed)
|
|
except Exception:
|
|
pass
|
|
|
|
heartbeat_task = asyncio.create_task(_heartbeat())
|
|
|
|
# mode→role 매핑: anime='operator'(도구 실행), task='agent'(코딩)
|
|
role = "operator" if mode == "anime" else "agent"
|
|
timeout = 600 # anime 배치 작업(20+건 순차)도 충분한 시간 확보
|
|
|
|
try:
|
|
result = await pipeline.execute(
|
|
text, history=history, progress_callback=_progress,
|
|
role=role,
|
|
)
|
|
finally:
|
|
_heartbeat_running[0] = False
|
|
try:
|
|
heartbeat_task.cancel()
|
|
except Exception:
|
|
pass
|
|
|
|
# ── 3. 결과 표시 ──
|
|
title = result.get("title", "작업 완료")
|
|
summary = result.get("summary", "완료")
|
|
verified = result.get("verified", False)
|
|
|
|
color = 0x2ECC71 if verified else 0xF39C12
|
|
result_embed = discord.Embed(
|
|
title=f"{'✅' if verified else '📋'} {title}",
|
|
description=summary[:2000],
|
|
color=color,
|
|
)
|
|
|
|
# 변경 사항
|
|
changes = result.get("changes", [])
|
|
if changes:
|
|
if isinstance(changes[0], dict):
|
|
val = "\n".join(
|
|
f"• **{c.get('title', c.get('file', c.get('name', '?')))}** — "
|
|
f"{c.get('action', c.get('description', c.get('summary', '')))}"
|
|
for c in changes[:10]
|
|
)
|
|
else:
|
|
val = "\n".join(f"• {s}" for s in changes[:10])
|
|
result_embed.add_field(name="변경 사항", value=val[:1000], inline=False)
|
|
|
|
# 주의사항
|
|
warnings = result.get("warnings", [])
|
|
if warnings:
|
|
result_embed.add_field(
|
|
name="⚠️ 주의",
|
|
value="\n".join(f"• {w}" for w in warnings[:5])[:1000],
|
|
inline=False,
|
|
)
|
|
|
|
# 다음 단계
|
|
next_steps = result.get("next_steps", [])
|
|
if next_steps:
|
|
result_embed.add_field(
|
|
name="🔜 다음 단계",
|
|
value="\n".join(f"• {s}" for s in next_steps[:5])[:1000],
|
|
inline=False,
|
|
)
|
|
|
|
result_embed.set_footer(text=f"ID: {task_id} | {ws.name}")
|
|
await status_msg.edit(embed=result_embed)
|
|
|
|
# 기록 (실패해도 봇 종료 방지)
|
|
try:
|
|
pipeline.docs.record_session(text, result, {})
|
|
pipeline.docs.append_changelog(title)
|
|
except Exception as doc_err:
|
|
logger.warning(f"세션 기록 실패: {doc_err}")
|
|
|
|
except GeminiCallError as e:
|
|
await status_msg.edit(
|
|
embed=discord.Embed(
|
|
title="❌ AI 호출 오류",
|
|
description=(
|
|
f"```{str(e)[:300]}```\n\n"
|
|
f"💡 요청을 더 짧게/구체적으로 다시 시도해보세요."
|
|
),
|
|
color=0xE74C3C,
|
|
)
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"작업 오류: {e}", exc_info=True)
|
|
await status_msg.edit(
|
|
embed=discord.Embed(
|
|
title="❌ 예기치 않은 오류",
|
|
description=f"```{str(e)[:300]}```",
|
|
color=0xE74C3C,
|
|
)
|
|
)
|