From 2cdd2e64a51edd21444f4a1568ae5ed9637acc75 Mon Sep 17 00:00:00 2001 From: modimihir07 Date: Mon, 29 Jun 2026 15:55:50 +0530 Subject: [PATCH] Fix chat 500 error, on_event deprecation, Error Dashboard 'Unknown' title MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - load_chat_history: handle malformed chat-history.json (list → dict) - save_chat_message: use setdefault instead of direct key access - chat endpoint: use local variable instead of mutating Pydantic model - Migrate from deprecated on_event to FastAPI lifespan context manager - list_dir: filter with p.is_file() to skip __pycache__/ - /api/brain: restrict to .md files only - gemini error handling: check combined stdout+stderr for auth errors - Add missing 'errors' entry to PAGE_TITLES (was showing 'Unknown') --- dashboard/utils.js | 1 + server.py | 91 ++++++++++++++++++++++++---------------------- 2 files changed, 49 insertions(+), 43 deletions(-) diff --git a/dashboard/utils.js b/dashboard/utils.js index 393f11e..717eee5 100644 --- a/dashboard/utils.js +++ b/dashboard/utils.js @@ -123,6 +123,7 @@ const PAGE_TITLES = { settings: { title: 'Settings', breadcrumb: 'Configuration' }, 'setup-wizard': { title: 'Setup Wizard', breadcrumb: 'Guided configuration' }, chat: { title: 'AI Chat', breadcrumb: 'Multi-agent terminal' }, + errors: { title: 'Error Dashboard', breadcrumb: 'System errors & circuit breaker' }, kanban: { title: 'Kanban Board', breadcrumb: 'Multi-agent task management' }, goals: { title: 'Goals', breadcrumb: 'Project targets and progress' }, journal: { title: 'Journal', breadcrumb: 'Daily entries and notes' }, diff --git a/server.py b/server.py index af1a04c..6cf5861 100644 --- a/server.py +++ b/server.py @@ -16,13 +16,34 @@ from datetime import datetime, timezone from pathlib import Path from typing import Optional +from contextlib import asynccontextmanager + from fastapi import FastAPI, HTTPException, Query from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import HTMLResponse, JSONResponse, Response from fastapi.staticfiles import StaticFiles from pydantic import BaseModel -app = FastAPI(title="Agentic OS", version="1.1.0") +_scheduler_instance = None + +@asynccontextmanager +async def lifespan(app: FastAPI): + global _scheduler_instance + try: + from scheduler.scheduler import CronScheduler + _scheduler_instance = CronScheduler() + _scheduler_instance.start() + print("Event-driven scheduler started") + except Exception as e: + print(f"Scheduler not available: {e}") + yield + if _scheduler_instance: + try: + _scheduler_instance.stop() + except Exception: + pass + +app = FastAPI(title="Agentic OS", version="1.1.0", lifespan=lifespan) # Load OpenRouter API key from Hermes .env HERMES_ENV = Path.home() / ".hermes" / ".env" @@ -84,7 +105,7 @@ def write_file(path: Path, content: str): def list_dir(path: Path): if not path.exists(): return [] - return sorted([p.name for p in path.iterdir() if not p.name.startswith(".")]) + return sorted([p.name for p in path.iterdir() if not p.name.startswith(".") and p.is_file()]) def get_timestamp(): return datetime.now(timezone.utc).isoformat() @@ -192,10 +213,13 @@ def get_status(): @app.get("/api/brain") def list_brain(): - files = list_dir(BASE_DIR / "brain") + brain_dir = BASE_DIR / "brain" + if not brain_dir.exists(): + return {} + files = sorted([p.name for p in brain_dir.iterdir() if p.name.endswith(".md") and p.is_file()]) brain_data = {} for f in files: - path = BASE_DIR / "brain" / f + path = brain_dir / f brain_data[f] = read_file(path) return brain_data @@ -751,13 +775,19 @@ def discover_standards(): CHAT_HISTORY_FILE = BASE_DIR / "data" / "chat-history.json" def load_chat_history(): - if CHAT_HISTORY_FILE.exists(): - return json.loads(CHAT_HISTORY_FILE.read_text()) + if not CHAT_HISTORY_FILE.exists(): + return {"messages": []} + try: + data = json.loads(CHAT_HISTORY_FILE.read_text()) + if isinstance(data, dict) and "messages" in data: + return data + except (json.JSONDecodeError, TypeError): + pass return {"messages": []} def save_chat_message(msg: dict): history = load_chat_history() - history["messages"].append(msg) + history.setdefault("messages", []).append(msg) if len(history["messages"]) > 200: history["messages"] = history["messages"][-200:] CHAT_HISTORY_FILE.write_text(json.dumps(history, indent=2)) @@ -836,23 +866,23 @@ def execute_agent(agent: str, message: str) -> str: elif agent == "gemini": for attempt, (args, to) in enumerate([ - (["-y", "-m", "gemini-2.5-flash"], 60), - (["-y"], 40), + (["-y", "-m", "gemini-2.5-flash"], 30), + (["-y"], 30), ]): try: code, out, err = run_cli(["gemini", *args, message], timeout=to) except subprocess.TimeoutExpired: if attempt == 0: continue - return f"⏱ Gemini timed out.\n\nTry running `gemini \"{message[:60]}\"` directly.\n\n**Message:** {message[:100]}" + return f"**Gemini CLI timed out.**\n\nTry running `gemini \"{message[:60]}\"` directly." + combined = ((err or "") + " " + (out or "")).strip() if code == 0: return (out or "").strip() or f"**Gemini CLI**\n\nProcessed your query.\n\n**Message:** {message}" - err_msg = (err or "").strip() - if attempt == 0 and ("model" in err_msg.lower() or "not found" in err_msg.lower()): + if attempt == 0 and ("model" in combined.lower() or "not found" in combined.lower()): continue - if "auth" in err_msg.lower() or "login" in err_msg.lower(): - return f"**Gemini needs re-auth**\n\nRun `gemini auth login` to re-authenticate.\n\n**Details:** {err_msg[:200]}" - return err_msg or f"gemini returned exit code {code}" + if "auth" in combined.lower() or "login" in combined.lower() or "Please set an Auth" in combined: + return f"**Gemini needs auth**\n\nRun `gemini auth login` to authenticate.\n\n**Details:** {combined[:200]}" + return combined or f"gemini returned exit code {code}" return "Gemini CLI did not return a response." else: @@ -874,18 +904,17 @@ def chat(req: ChatRequest): raise HTTPException(400, "Message cannot be empty") if len(message) > 10000: raise HTTPException(400, "Message too long (max 10000 characters)") - req.message = message user_msg = { "id": str(uuid.uuid4())[:8], "role": "user", "agent": agent, - "content": req.message, + "content": message, "timestamp": get_timestamp(), } save_chat_message(user_msg) - response_text = execute_agent(agent, req.message) + response_text = execute_agent(agent, message) agent_msg = { "id": str(uuid.uuid4())[:8], @@ -896,7 +925,7 @@ def chat(req: ChatRequest): } save_chat_message(agent_msg) - append_audit({"action": "chat_message", "agent": agent, "msg_preview": req.message[:50]}) + append_audit({"action": "chat_message", "agent": agent, "msg_preview": message[:50]}) return {"status": "ok", "response": agent_msg} @@ -1518,30 +1547,6 @@ def favicon(): def favicon_svg(): return Response(content=FAVICON_SVG, media_type="image/svg+xml") -# ─── Scheduler Auto-Start (v0.3.0) ───────────────────────────────── - -_scheduler_instance = None - -@app.on_event("startup") -def start_scheduler(): - global _scheduler_instance - try: - from scheduler.scheduler import CronScheduler - _scheduler_instance = CronScheduler() - _scheduler_instance.start() - print("Event-driven scheduler started") - except Exception as e: - print(f"Scheduler not available: {e}") - -@app.on_event("shutdown") -def stop_scheduler(): - global _scheduler_instance - if _scheduler_instance: - try: - _scheduler_instance.stop() - except Exception: - pass - # ─── PWA Support (v0.3.0) ────────────────────────────────────────── MANIFEST_JSON = {