"""Webhook 接收 API。""" import json import re from datetime import datetime, timezone from fastapi import APIRouter, Depends from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select from ..core.database import get_db from ..models import Signal from ..services.llm_service import llm_service from ..services.integration_core.plane_service import plane_service from ..utils.timezone import now_cn_aware router = APIRouter(prefix="/webhooks", tags=["webhooks"]) @router.post("/git") async def handle_git_webhook( payload: dict, db: AsyncSession = Depends(get_db), ): """接收 Git Webhook,审计代码并更新状态""" commits = payload.get("commits", []) if not commits: return {"status": "ignored", "reason": "No commits found"} updated_tasks = [] for commit in commits: message = commit.get("message", "") code_diff = commit.get("patch") or str(commit.get("modified", [])) task_matches = re.findall( r"#([A-Za-z0-9-]+-\d+|[A-Za-z0-9]+-\d+)", message ) for task_id in task_matches: result = await db.execute( select(Signal).order_by(Signal.created_at.desc()).limit(20) ) signals = result.scalars().all() target_task = None target_signal = None for sig in signals: if not sig.tasks_generated: continue try: tasks = json.loads(sig.tasks_generated) for t in tasks: if t.get("plane_key") == task_id or t.get( "plane_id" ) == task_id: target_task = t target_signal = sig break except Exception: continue if target_task: break task_name = ( target_task.get("task_name") if target_task else f"Task {task_id}" ) # 尽量从目标信号推断租户,避免跨租户预算串账。 tenant_id = int(getattr(target_signal, "tenant_id", 1) or 1) try: audit_result = await llm_service.audit_code_change_async( task_name, message, code_diff, db=db, tenant_id=tenant_id, ) except RuntimeError: updated_tasks.append( { "task_id": task_id, "status": "Blocked", "audit": {"passed": False, "reason": "预算超限,AI 审计被阻止"}, } ) continue if audit_result.get("passed"): await plane_service.update_issue_status(task_id, status="Done") if target_signal and target_task: tasks = json.loads(target_signal.tasks_generated) for t in tasks: if t.get("plane_key") == task_id or t.get( "plane_id" ) == task_id: t["status"] = "Done" t["audit_feedback"] = audit_result.get("reason") t["completed_at"] = ( now_cn_aware().isoformat() ) target_signal.tasks_generated = json.dumps(tasks) await db.commit() updated_tasks.append( {"task_id": task_id, "status": "Done", "audit": audit_result} ) else: updated_tasks.append( { "task_id": task_id, "status": "Failed", "audit": audit_result, } ) return {"status": "processed", "updated_tasks": updated_tasks}