Phase 2: Chat History for Agent Tab

Backend: _get_chat_history() reads JSONL sessions. API: GET /api/agents/{id}/chat-history. Frontend: Chat History tab shows session list with title, model, message count. Click to open session in chat panel.
This commit is contained in:
Rose
2026-04-20 13:33:24 +02:00
parent fbf79362a4
commit e5b55c6f3a
4 changed files with 179 additions and 1 deletions

View File

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

View File

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