From b3809f5246f47797fe1ea4c717b0b9a1c88f58b8 Mon Sep 17 00:00:00 2001 From: Kimi Agent Date: Sat, 14 Mar 2026 19:23:32 -0400 Subject: [PATCH] feat: add JSON status endpoints for briefing, memory, swarm (#49, #50) --- src/dashboard/routes/system.py | 81 ++++++++++++++++++++ tests/dashboard/test_api_status_endpoints.py | 77 +++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 tests/dashboard/test_api_status_endpoints.py diff --git a/src/dashboard/routes/system.py b/src/dashboard/routes/system.py index 86ac817..e05ddac 100644 --- a/src/dashboard/routes/system.py +++ b/src/dashboard/routes/system.py @@ -1,10 +1,12 @@ """System-level dashboard routes (ledger, upgrades, etc.).""" import logging +from pathlib import Path from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse, JSONResponse +from config import settings from dashboard.templating import templates logger = logging.getLogger(__name__) @@ -146,3 +148,82 @@ async def api_notifications(): ) except Exception: return JSONResponse([]) + + +@router.get("/api/briefing/status", response_class=JSONResponse) +async def api_briefing_status(): + """Return briefing status including pending approvals and last generated time.""" + from timmy import approvals + from timmy.briefing import engine as briefing_engine + + pending = approvals.list_pending() + pending_count = len(pending) + + last_generated = None + try: + cached = briefing_engine.get_cached() + if cached: + last_generated = cached.generated_at.isoformat() + except Exception: + pass + + return JSONResponse( + { + "status": "ok", + "pending_approvals": pending_count, + "last_generated": last_generated, + } + ) + + +@router.get("/api/memory/status", response_class=JSONResponse) +async def api_memory_status(): + """Return memory database status including file info and indexed files count.""" + from timmy.memory.vector_store import get_memory_stats + + db_path = Path(settings.repo_root) / "data" / "memory.db" + db_exists = db_path.exists() + db_size = db_path.stat().st_size if db_exists else 0 + + try: + stats = get_memory_stats() + indexed_files = stats.get("total_entries", 0) + except Exception: + indexed_files = 0 + + return JSONResponse( + { + "status": "ok", + "db_exists": db_exists, + "db_size_bytes": db_size, + "indexed_files": indexed_files, + } + ) + + +@router.get("/api/swarm/status", response_class=JSONResponse) +async def api_swarm_status(): + """Return swarm worker status and pending tasks count.""" + from dashboard.routes.tasks import _get_db + + pending_tasks = 0 + try: + db = _get_db() + try: + row = db.execute( + "SELECT COUNT(*) as cnt FROM tasks WHERE status IN ('pending_approval','approved')" + ).fetchone() + pending_tasks = row["cnt"] if row else 0 + finally: + db.close() + except Exception: + pass + + return JSONResponse( + { + "status": "ok", + "active_workers": 0, + "pending_tasks": pending_tasks, + "message": "Swarm monitoring endpoint", + } + ) diff --git a/tests/dashboard/test_api_status_endpoints.py b/tests/dashboard/test_api_status_endpoints.py new file mode 100644 index 0000000..b5a9490 --- /dev/null +++ b/tests/dashboard/test_api_status_endpoints.py @@ -0,0 +1,77 @@ +"""Tests for the API status endpoints. + +Verifies /api/briefing/status, /api/memory/status, and /api/swarm/status +return valid JSON with expected keys. +""" + + +def test_api_briefing_status_returns_ok(client): + """GET /api/briefing/status returns 200 with expected JSON structure.""" + response = client.get("/api/briefing/status") + assert response.status_code == 200 + + data = response.json() + assert data["status"] == "ok" + assert "pending_approvals" in data + assert isinstance(data["pending_approvals"], int) + assert "last_generated" in data + # last_generated can be None or a string + assert data["last_generated"] is None or isinstance(data["last_generated"], str) + + +def test_api_memory_status_returns_ok(client): + """GET /api/memory/status returns 200 with expected JSON structure.""" + response = client.get("/api/memory/status") + assert response.status_code == 200 + + data = response.json() + assert data["status"] == "ok" + assert "db_exists" in data + assert isinstance(data["db_exists"], bool) + assert "db_size_bytes" in data + assert isinstance(data["db_size_bytes"], int) + assert data["db_size_bytes"] >= 0 + assert "indexed_files" in data + assert isinstance(data["indexed_files"], int) + assert data["indexed_files"] >= 0 + + +def test_api_swarm_status_returns_ok(client): + """GET /api/swarm/status returns 200 with expected JSON structure.""" + response = client.get("/api/swarm/status") + assert response.status_code == 200 + + data = response.json() + assert data["status"] == "ok" + assert "active_workers" in data + assert isinstance(data["active_workers"], int) + assert "pending_tasks" in data + assert isinstance(data["pending_tasks"], int) + assert data["pending_tasks"] >= 0 + assert "message" in data + assert isinstance(data["message"], str) + assert data["message"] == "Swarm monitoring endpoint" + + +def test_api_swarm_status_reflects_pending_tasks(client): + """GET /api/swarm/status reflects pending tasks from task queue.""" + # First create a task + client.post("/api/tasks", json={"title": "Swarm status test task"}) + + # Now check swarm status + response = client.get("/api/swarm/status") + assert response.status_code == 200 + + data = response.json() + assert data["pending_tasks"] >= 1 + + +def test_api_briefing_status_pending_approvals_count(client): + """GET /api/briefing/status returns correct pending approvals count.""" + response = client.get("/api/briefing/status") + assert response.status_code == 200 + + data = response.json() + assert "pending_approvals" in data + assert isinstance(data["pending_approvals"], int) + assert data["pending_approvals"] >= 0