Fix chat 500 error, on_event deprecation, Error Dashboard 'Unknown' title

- 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')
This commit is contained in:
modimihir07 2026-06-29 15:55:50 +05:30
parent a7dce93e74
commit 2cdd2e64a5
2 changed files with 49 additions and 43 deletions

View File

@ -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' },

View File

@ -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 = {