from fastapi import FastAPI from contextlib import asynccontextmanager import logging from .db import init_db from .webhooks.plane import router as plane_router from .webhooks.gitea import router as gitea_router # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", ) @asynccontextmanager async def lifespan(app: FastAPI): init_db() # Recover orphaned runs from .db import get_db conn = get_db() orphans = conn.execute( "UPDATE agent_runs SET finished_at=datetime('now'), exit_code=-1 " "WHERE finished_at IS NULL AND started_at < datetime('now', '-35 minutes')" ).rowcount conn.commit() conn.close() if orphans: logging.getLogger('orchestrator').warning(f'Recovered {orphans} orphaned agent runs') yield app = FastAPI(title="Multi-Agent Orchestrator", lifespan=lifespan) app.include_router(plane_router, prefix="/webhook") app.include_router(gitea_router, prefix="/webhook") @app.get("/health") async def health(): return {"status": "ok", "service": "orchestrator"} @app.get("/status") async def status(): from .db import get_db conn = get_db() tasks = conn.execute( "SELECT * FROM tasks WHERE stage != 'done' ORDER BY created_at DESC LIMIT 10" ).fetchall() conn.close() return {"active_tasks": [dict(t) for t in tasks]}