diff --git a/api/agents.py b/api/agents.py index fb131a9..e5867f7 100644 --- a/api/agents.py +++ b/api/agents.py @@ -514,3 +514,86 @@ def get_agent_errors(agent_id: str, limit: int = 20) -> dict: "agent_id": agent_id, "errors": errors, } + + +# ── Chat History ────────────────────────────────────────────────────────────── + +def _get_chat_history(agent_id: str, limit: int = 20) -> list[dict]: + """ + Read chat sessions from JSONL files and return history for a specific agent. + Sessions are sorted newest-first. + Returns list of {session_id, title, message_count, created_at, last_message_at, model}. + """ + sessions_dir = _HERMES_DIR / "sessions" + if not sessions_dir.exists(): + return [] + + sessions = sorted(sessions_dir.glob("*.jsonl"), key=lambda p: p.stat().st_mtime, reverse=True) + history = [] + + for session_file in sessions[:limit * 2]: # overscan + if len(history) >= limit: + break + try: + with open(session_file) as f: + lines = f.readlines() + + if not lines: + continue + + # First line has metadata + metadata = json.loads(lines[0]) + created_at = metadata.get("timestamp", "") + model = metadata.get("model", "unknown") + + # Count messages + message_count = sum(1 for l in lines if l.strip()) + + # Title = first user message preview + title = "Chat" + for line in lines[1:]: + if line.strip(): + try: + msg = json.loads(line) + if msg.get("role") == "user": + content = str(msg.get("content", ""))[:80] + title = content if content else "Chat" + break + except Exception: + pass + + # Last message timestamp + last_msg = None + for line in reversed(lines): + if line.strip(): + try: + last_msg = json.loads(line).get("timestamp", created_at) + break + except Exception: + pass + + session_id = session_file.stem # filename without .jsonl + + history.append({ + "session_id": session_id, + "title": title, + "message_count": message_count, + "created_at": created_at, + "last_message_at": last_msg or created_at, + "model": model, + }) + except Exception: + continue + + return history[:limit] + + +def get_agent_chat_history(agent_id: str, limit: int = 20) -> dict: + """API: GET /api/agents/{id}/chat-history — return chat history for agent.""" + if agent_id not in TIER2_AGENTS and agent_id != "rose": + return {"error": f"Unknown agent: {agent_id}"} + history = _get_chat_history(agent_id, limit) + return { + "agent_id": agent_id, + "sessions": history, + } diff --git a/api/routes.py b/api/routes.py index fad1810..2044d49 100644 --- a/api/routes.py +++ b/api/routes.py @@ -859,6 +859,14 @@ def handle_get(handler, parsed) -> bool: limit = int(parse_qs(parsed.query).get("limit", ["20"])[0]) return j(handler, _agents.get_agent_errors(agent_id, limit=limit)) + # GET /api/agents/{id}/chat-history + if parsed.path.startswith("/api/agents/") and "/chat-history" in parsed.path: + parts = parsed.path.split("/") + if len(parts) == 5 and parts[4] == "chat-history": + agent_id = parts[3] + limit = int(parse_qs(parsed.query).get("limit", ["20"])[0]) + return j(handler, _agents.get_agent_chat_history(agent_id, limit=limit)) + # ── Profile API (GET) ── if parsed.path == "/api/profiles": from api.profiles import list_profiles_api, get_active_profile_name diff --git a/static/panels.js b/static/panels.js index 0f7a2fc..56ef272 100644 --- a/static/panels.js +++ b/static/panels.js @@ -1917,6 +1917,7 @@ async function openAgentDetail(agentId) { +