Spaces:
Sleeping
Sleeping
Claude Code commited on
Commit ·
9a0d91f
1
Parent(s): 2f8fc36
Claude Code: Create a "System Diagnostics" tab in that parses and displays t
Browse files- __pycache__/app.cpython-311.pyc +0 -0
- __pycache__/gradio_dashboard.cpython-311.pyc +0 -0
- app.py +77 -0
- gradio_dashboard.py +303 -0
__pycache__/app.cpython-311.pyc
ADDED
|
Binary file (25.2 kB). View file
|
|
|
__pycache__/gradio_dashboard.cpython-311.pyc
CHANGED
|
Binary files a/__pycache__/gradio_dashboard.cpython-311.pyc and b/__pycache__/gradio_dashboard.cpython-311.pyc differ
|
|
|
app.py
CHANGED
|
@@ -138,6 +138,9 @@ async def root():
|
|
| 138 |
"chat": "/api/chat",
|
| 139 |
"health": "/api/health",
|
| 140 |
"tools": "/api/tools",
|
|
|
|
|
|
|
|
|
|
| 141 |
"docs": "/docs"
|
| 142 |
},
|
| 143 |
"gradio_dashboard": "/gradio",
|
|
@@ -396,6 +399,80 @@ async def get_logs(
|
|
| 396 |
except Exception as e:
|
| 397 |
raise HTTPException(status_code=500, detail=f"Error getting logs: {str(e)}")
|
| 398 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
@app.post("/api/reset", tags=["Administration"])
|
| 400 |
async def reset_agent():
|
| 401 |
"""
|
|
|
|
| 138 |
"chat": "/api/chat",
|
| 139 |
"health": "/api/health",
|
| 140 |
"tools": "/api/tools",
|
| 141 |
+
"logs": "/api/logs",
|
| 142 |
+
"diagnostics": "/api/diagnostics",
|
| 143 |
+
"reset": "/api/reset",
|
| 144 |
"docs": "/docs"
|
| 145 |
},
|
| 146 |
"gradio_dashboard": "/gradio",
|
|
|
|
| 399 |
except Exception as e:
|
| 400 |
raise HTTPException(status_code=500, detail=f"Error getting logs: {str(e)}")
|
| 401 |
|
| 402 |
+
@app.get("/api/diagnostics", tags=["Monitoring"])
|
| 403 |
+
async def get_diagnostics(limit: int = 100):
|
| 404 |
+
"""
|
| 405 |
+
Get system diagnostics data including session archive logs and health status.
|
| 406 |
+
|
| 407 |
+
Returns data from:
|
| 408 |
+
- session-archive.jsonl (agent state changes)
|
| 409 |
+
- Health monitor component status
|
| 410 |
+
"""
|
| 411 |
+
result = {
|
| 412 |
+
"timestamp": datetime.utcnow().isoformat() + "Z",
|
| 413 |
+
"session_archive": {},
|
| 414 |
+
"health_monitor": {}
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
# Read session archive logs
|
| 418 |
+
session_archive_path = BASE_DIR / "agents" / "logs" / "session-archive.jsonl"
|
| 419 |
+
if session_archive_path.exists():
|
| 420 |
+
try:
|
| 421 |
+
entries = []
|
| 422 |
+
with open(session_archive_path, 'r') as f:
|
| 423 |
+
lines = f.readlines()
|
| 424 |
+
|
| 425 |
+
# Parse JSONL entries (most recent first)
|
| 426 |
+
for line in reversed(lines[-limit:]):
|
| 427 |
+
line = line.strip()
|
| 428 |
+
if not line:
|
| 429 |
+
continue
|
| 430 |
+
try:
|
| 431 |
+
entries.append(json.loads(line))
|
| 432 |
+
except json.JSONDecodeError:
|
| 433 |
+
pass
|
| 434 |
+
|
| 435 |
+
result["session_archive"] = {
|
| 436 |
+
"file_path": str(session_archive_path),
|
| 437 |
+
"total_lines": len(lines),
|
| 438 |
+
"entries_shown": len(entries),
|
| 439 |
+
"entries": entries
|
| 440 |
+
}
|
| 441 |
+
except Exception as e:
|
| 442 |
+
result["session_archive"] = {
|
| 443 |
+
"error": str(e),
|
| 444 |
+
"file_path": str(session_archive_path)
|
| 445 |
+
}
|
| 446 |
+
else:
|
| 447 |
+
result["session_archive"] = {
|
| 448 |
+
"error": "File not found",
|
| 449 |
+
"file_path": str(session_archive_path)
|
| 450 |
+
}
|
| 451 |
+
|
| 452 |
+
# Get health monitor status
|
| 453 |
+
monitor = get_health_monitor_instance()
|
| 454 |
+
if monitor:
|
| 455 |
+
try:
|
| 456 |
+
health_results = monitor.run_all_health_checks()
|
| 457 |
+
result["health_monitor"] = {
|
| 458 |
+
"overall_status": health_results.get("overall_status"),
|
| 459 |
+
"uptime_seconds": health_results.get("uptime_seconds"),
|
| 460 |
+
"check_count": health_results.get("check_count"),
|
| 461 |
+
"components": {
|
| 462 |
+
name: {
|
| 463 |
+
"status": result["status"].value if isinstance(result["status"], HealthStatus) else result["status"],
|
| 464 |
+
"details": result.get("details", {})
|
| 465 |
+
}
|
| 466 |
+
for name, result in health_results.get("components", {}).items()
|
| 467 |
+
}
|
| 468 |
+
}
|
| 469 |
+
except Exception as e:
|
| 470 |
+
result["health_monitor"] = {"error": str(e)}
|
| 471 |
+
else:
|
| 472 |
+
result["health_monitor"] = {"error": "Health monitor not available"}
|
| 473 |
+
|
| 474 |
+
return result
|
| 475 |
+
|
| 476 |
@app.post("/api/reset", tags=["Administration"])
|
| 477 |
async def reset_agent():
|
| 478 |
"""
|
gradio_dashboard.py
CHANGED
|
@@ -38,6 +38,8 @@ CRON_LOGS_DIR = BASE_DIR / "logs"
|
|
| 38 |
CRON_JOBS_FILE = BASE_DIR / "cron" / "jobs.json"
|
| 39 |
MEMORY_STATE_FILE = Path("/data/memory/state.json")
|
| 40 |
WORKSPACE_LOGS = BASE_DIR / "workspace"
|
|
|
|
|
|
|
| 41 |
|
| 42 |
|
| 43 |
# ========== API Client Functions ==========
|
|
@@ -323,6 +325,240 @@ def get_health_logs(level: str = "all", component: str = "all", limit: int = 100
|
|
| 323 |
return [{"error": str(e)}]
|
| 324 |
|
| 325 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 326 |
# ========== Formatting Functions ==========
|
| 327 |
|
| 328 |
def format_agent_states(states):
|
|
@@ -633,6 +869,57 @@ def create_dashboard():
|
|
| 633 |
value={}
|
| 634 |
)
|
| 635 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 636 |
# Footer with refresh
|
| 637 |
with gr.Row():
|
| 638 |
refresh_btn = gr.Button("🔄 Refresh Dashboard", variant="primary")
|
|
@@ -650,6 +937,9 @@ def create_dashboard():
|
|
| 650 |
]
|
| 651 |
)
|
| 652 |
|
|
|
|
|
|
|
|
|
|
| 653 |
# Manual refresh for main dashboard
|
| 654 |
refresh_btn.click(
|
| 655 |
fn=refresh_all,
|
|
@@ -689,6 +979,19 @@ def create_dashboard():
|
|
| 689 |
outputs=[health_json, logs_table, health_report, last_updated]
|
| 690 |
)
|
| 691 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 692 |
return app
|
| 693 |
|
| 694 |
|
|
|
|
| 38 |
CRON_JOBS_FILE = BASE_DIR / "cron" / "jobs.json"
|
| 39 |
MEMORY_STATE_FILE = Path("/data/memory/state.json")
|
| 40 |
WORKSPACE_LOGS = BASE_DIR / "workspace"
|
| 41 |
+
SESSION_ARCHIVE_FILE = BASE_DIR / "agents" / "logs" / "session-archive.jsonl"
|
| 42 |
+
HEALTH_MONITOR_FILE = BASE_DIR / "health_monitor.py"
|
| 43 |
|
| 44 |
|
| 45 |
# ========== API Client Functions ==========
|
|
|
|
| 325 |
return [{"error": str(e)}]
|
| 326 |
|
| 327 |
|
| 328 |
+
# ========== Diagnostic Functions ==========
|
| 329 |
+
|
| 330 |
+
def get_session_archive_logs(limit: int = 100):
|
| 331 |
+
"""Read and parse agent logs from session-archive.jsonl (fallback direct read)"""
|
| 332 |
+
logs = []
|
| 333 |
+
|
| 334 |
+
if not SESSION_ARCHIVE_FILE.exists():
|
| 335 |
+
return {
|
| 336 |
+
"error": f"Session archive file not found: {SESSION_ARCHIVE_FILE}",
|
| 337 |
+
"file_path": str(SESSION_ARCHIVE_FILE),
|
| 338 |
+
"entries": []
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
try:
|
| 342 |
+
with open(SESSION_ARCHIVE_FILE, 'r') as f:
|
| 343 |
+
lines = f.readlines()
|
| 344 |
+
|
| 345 |
+
# Parse JSONL entries (most recent first)
|
| 346 |
+
for line in reversed(lines[-limit:]):
|
| 347 |
+
line = line.strip()
|
| 348 |
+
if not line:
|
| 349 |
+
continue
|
| 350 |
+
|
| 351 |
+
try:
|
| 352 |
+
entry = json.loads(line)
|
| 353 |
+
logs.append(entry)
|
| 354 |
+
except json.JSONDecodeError as e:
|
| 355 |
+
logs.append({
|
| 356 |
+
"error": f"Failed to parse line: {str(e)}",
|
| 357 |
+
"raw_line": line[:200]
|
| 358 |
+
})
|
| 359 |
+
|
| 360 |
+
return {
|
| 361 |
+
"file_path": str(SESSION_ARCHIVE_FILE),
|
| 362 |
+
"total_lines": len(lines),
|
| 363 |
+
"entries_shown": len(logs),
|
| 364 |
+
"entries": logs
|
| 365 |
+
}
|
| 366 |
+
except Exception as e:
|
| 367 |
+
return {
|
| 368 |
+
"error": f"Failed to read session archive: {str(e)}",
|
| 369 |
+
"file_path": str(SESSION_ARCHIVE_FILE),
|
| 370 |
+
"entries": []
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
def get_health_monitor_status():
|
| 375 |
+
"""Get health monitor status by running health checks (fallback direct read)"""
|
| 376 |
+
status = {
|
| 377 |
+
"monitor_file_exists": HEALTH_MONITOR_FILE.exists(),
|
| 378 |
+
"monitor_file_path": str(HEALTH_MONITOR_FILE),
|
| 379 |
+
"health_checks": {}
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
if not HEALTH_MONITOR_FILE.exists():
|
| 383 |
+
status["error"] = "Health monitor file not found"
|
| 384 |
+
return status
|
| 385 |
+
|
| 386 |
+
# Try to get health data from API
|
| 387 |
+
try:
|
| 388 |
+
health_data = get_system_health()
|
| 389 |
+
|
| 390 |
+
if "error" in health_data:
|
| 391 |
+
status["api_error"] = health_data["error"]
|
| 392 |
+
else:
|
| 393 |
+
status["overall_status"] = health_data.get("status", "unknown")
|
| 394 |
+
status["stage"] = health_data.get("stage", "UNKNOWN")
|
| 395 |
+
status["uptime_seconds"] = health_data.get("uptime_seconds", 0)
|
| 396 |
+
status["agent"] = health_data.get("agent", "cain")
|
| 397 |
+
status["components"] = health_data.get("components", {})
|
| 398 |
+
|
| 399 |
+
# Extract component health details
|
| 400 |
+
for comp_name, comp_data in status.get("components", {}).items():
|
| 401 |
+
if isinstance(comp_data, dict) and "status" in comp_data:
|
| 402 |
+
status["health_checks"][comp_name] = {
|
| 403 |
+
"status": comp_data["status"],
|
| 404 |
+
"details": comp_data.get("details", {})
|
| 405 |
+
}
|
| 406 |
+
except Exception as e:
|
| 407 |
+
status["error"] = f"Failed to get health status: {str(e)}"
|
| 408 |
+
|
| 409 |
+
return status
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
def get_diagnostics_from_api(limit: int = 100):
|
| 413 |
+
"""Get diagnostics data from the /api/diagnostics endpoint (preferred method)"""
|
| 414 |
+
try:
|
| 415 |
+
params = f"limit={limit}"
|
| 416 |
+
endpoint = f"/api/diagnostics?{params}"
|
| 417 |
+
data = fetch_api(endpoint)
|
| 418 |
+
|
| 419 |
+
if "error" in data:
|
| 420 |
+
return None # Fallback to direct file read
|
| 421 |
+
|
| 422 |
+
return data
|
| 423 |
+
except Exception:
|
| 424 |
+
return None # Fallback to direct file read
|
| 425 |
+
|
| 426 |
+
|
| 427 |
+
def format_session_logs_for_table(log_data):
|
| 428 |
+
"""Format session archive logs for table display"""
|
| 429 |
+
rows = []
|
| 430 |
+
|
| 431 |
+
entries = log_data.get("entries", [])
|
| 432 |
+
|
| 433 |
+
for entry in entries:
|
| 434 |
+
if "error" in entry:
|
| 435 |
+
rows.append([
|
| 436 |
+
"N/A",
|
| 437 |
+
"error",
|
| 438 |
+
entry.get("raw_line", "")[:100],
|
| 439 |
+
"N/A"
|
| 440 |
+
])
|
| 441 |
+
continue
|
| 442 |
+
|
| 443 |
+
# Handle different entry types
|
| 444 |
+
entry_type = entry.get("type", "unknown")
|
| 445 |
+
timestamp = entry.get("timestamp", "N/A")
|
| 446 |
+
|
| 447 |
+
if entry_type == "state_change":
|
| 448 |
+
agent = entry.get("agent", "unknown")
|
| 449 |
+
state = entry.get("state", "unknown")
|
| 450 |
+
message = f"{agent} -> {state}"
|
| 451 |
+
rows.append([timestamp, entry_type, message, agent])
|
| 452 |
+
elif entry.get("action") == "archive_sessions":
|
| 453 |
+
status = entry.get("status", "unknown")
|
| 454 |
+
archived = entry.get("archived_count", 0)
|
| 455 |
+
message = f"Archive: {status}, archived: {archived}"
|
| 456 |
+
rows.append([timestamp, "archive", message, "system"])
|
| 457 |
+
else:
|
| 458 |
+
# Generic entry
|
| 459 |
+
message = json.dumps(entry, indent=2)[:150]
|
| 460 |
+
rows.append([timestamp, entry_type, message, "various"])
|
| 461 |
+
|
| 462 |
+
return rows[:50] # Limit to 50 rows for display
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
def format_diagnostics_markdown(session_data, health_data):
|
| 466 |
+
"""Format diagnostic data as Markdown"""
|
| 467 |
+
lines = [
|
| 468 |
+
"# System Diagnostics Report",
|
| 469 |
+
f"Generated: {datetime.utcnow().isoformat()}Z",
|
| 470 |
+
"",
|
| 471 |
+
"## Session Archive Logs",
|
| 472 |
+
f"- **File Path:** `{session_data.get('file_path', 'N/A')}`",
|
| 473 |
+
f"- **Total Lines:** {session_data.get('total_lines', 0)}",
|
| 474 |
+
f"- **Entries Shown:** {session_data.get('entries_shown', 0)}",
|
| 475 |
+
""
|
| 476 |
+
]
|
| 477 |
+
|
| 478 |
+
if "error" in session_data:
|
| 479 |
+
lines.append(f"**Error:** {session_data['error']}")
|
| 480 |
+
lines.append("")
|
| 481 |
+
|
| 482 |
+
# Show recent entries
|
| 483 |
+
entries = session_data.get("entries", [])[:20]
|
| 484 |
+
if entries:
|
| 485 |
+
lines.append("### Recent Entries")
|
| 486 |
+
for i, entry in enumerate(entries, 1):
|
| 487 |
+
if "error" in entry:
|
| 488 |
+
lines.append(f"{i}. **Error:** {entry.get('error', 'Unknown error')}")
|
| 489 |
+
else:
|
| 490 |
+
entry_type = entry.get("type", "unknown")
|
| 491 |
+
timestamp = entry.get("timestamp", "N/A")
|
| 492 |
+
lines.append(f"{i}. **[{entry_type}]** {timestamp}")
|
| 493 |
+
lines.append(f" ```json")
|
| 494 |
+
lines.append(f" {json.dumps(entry, indent=2)[:200]}...")
|
| 495 |
+
lines.append(f" ```")
|
| 496 |
+
else:
|
| 497 |
+
lines.append("No recent entries found.")
|
| 498 |
+
|
| 499 |
+
lines.extend([
|
| 500 |
+
"",
|
| 501 |
+
"## Health Monitor Status",
|
| 502 |
+
f"- **Monitor File:** `{health_data.get('monitor_file_path', 'N/A')}`",
|
| 503 |
+
f"- **File Exists:** {health_data.get('monitor_file_exists', False)}",
|
| 504 |
+
""
|
| 505 |
+
])
|
| 506 |
+
|
| 507 |
+
if "overall_status" in health_data:
|
| 508 |
+
lines.extend([
|
| 509 |
+
f"- **Overall Status:** {health_data.get('overall_status', 'unknown')}",
|
| 510 |
+
f"- **Stage:** {health_data.get('stage', 'UNKNOWN')}",
|
| 511 |
+
f"- **Agent:** {health_data.get('agent', 'cain')}",
|
| 512 |
+
f"- **Uptime:** {health_data.get('uptime_seconds', 0):.1f}s",
|
| 513 |
+
""
|
| 514 |
+
])
|
| 515 |
+
|
| 516 |
+
if "error" in health_data:
|
| 517 |
+
lines.append(f"**Error:** {health_data['error']}")
|
| 518 |
+
lines.append("")
|
| 519 |
+
|
| 520 |
+
# Show component health
|
| 521 |
+
health_checks = health_data.get("health_checks", {})
|
| 522 |
+
if health_checks:
|
| 523 |
+
lines.append("### Component Health Checks")
|
| 524 |
+
for comp_name, comp_data in health_checks.items():
|
| 525 |
+
status = comp_data.get("status", "unknown")
|
| 526 |
+
lines.append(f"- **{comp_name}:** {status}")
|
| 527 |
+
if comp_data.get("details"):
|
| 528 |
+
details_str = json.dumps(comp_data["details"], indent=2)[:200]
|
| 529 |
+
lines.append(f" ```json")
|
| 530 |
+
lines.append(f" {details_str}")
|
| 531 |
+
lines.append(f" ```")
|
| 532 |
+
else:
|
| 533 |
+
lines.append("No component health data available.")
|
| 534 |
+
|
| 535 |
+
return "\n".join(lines)
|
| 536 |
+
|
| 537 |
+
|
| 538 |
+
def refresh_diagnostics():
|
| 539 |
+
"""Refresh all diagnostic data"""
|
| 540 |
+
# Try API first, fallback to direct file reads
|
| 541 |
+
api_data = get_diagnostics_from_api(limit=100)
|
| 542 |
+
|
| 543 |
+
if api_data:
|
| 544 |
+
# Use data from API
|
| 545 |
+
session_data = api_data.get("session_archive", {})
|
| 546 |
+
health_data = api_data.get("health_monitor", {})
|
| 547 |
+
else:
|
| 548 |
+
# Fallback to direct file reads
|
| 549 |
+
session_data = get_session_archive_logs(limit=100)
|
| 550 |
+
health_data = get_health_monitor_status()
|
| 551 |
+
|
| 552 |
+
# Format outputs
|
| 553 |
+
session_json = json.dumps(session_data, indent=2, default=str)
|
| 554 |
+
health_json = json.dumps(health_data, indent=2, default=str)
|
| 555 |
+
session_table = format_session_logs_for_table(session_data)
|
| 556 |
+
diagnostics_md = format_diagnostics_markdown(session_data, health_data)
|
| 557 |
+
timestamp = f"Last updated: {datetime.utcnow().isoformat()}Z"
|
| 558 |
+
|
| 559 |
+
return session_json, health_json, session_table, diagnostics_md, timestamp
|
| 560 |
+
|
| 561 |
+
|
| 562 |
# ========== Formatting Functions ==========
|
| 563 |
|
| 564 |
def format_agent_states(states):
|
|
|
|
| 869 |
value={}
|
| 870 |
)
|
| 871 |
|
| 872 |
+
# System Diagnostics Tab (NEW)
|
| 873 |
+
with gr.Tab("🔍 System Diagnostics"):
|
| 874 |
+
with gr.Row():
|
| 875 |
+
refresh_diagnostics_btn = gr.Button("🔍 Refresh Diagnostics", variant="primary")
|
| 876 |
+
diagnostics_last_updated = gr.Markdown("Click refresh to load")
|
| 877 |
+
|
| 878 |
+
gr.Markdown("### Session Archive Logs")
|
| 879 |
+
gr.Markdown("Agent state changes and archived session data from `session-archive.jsonl`")
|
| 880 |
+
|
| 881 |
+
with gr.Row():
|
| 882 |
+
with gr.Column():
|
| 883 |
+
session_logs_table = gr.DataFrame(
|
| 884 |
+
label="Recent Session Logs",
|
| 885 |
+
headers=["Timestamp", "Type", "Message", "Agent"],
|
| 886 |
+
value=[],
|
| 887 |
+
interactive=False
|
| 888 |
+
)
|
| 889 |
+
with gr.Column():
|
| 890 |
+
session_logs_json = gr.JSON(
|
| 891 |
+
label="Raw Session Logs (JSON)",
|
| 892 |
+
value={}
|
| 893 |
+
)
|
| 894 |
+
|
| 895 |
+
gr.Markdown("---")
|
| 896 |
+
|
| 897 |
+
gr.Markdown("### Health Monitor Status")
|
| 898 |
+
gr.Markdown("Health check status from the health monitoring system")
|
| 899 |
+
|
| 900 |
+
with gr.Row():
|
| 901 |
+
with gr.Column():
|
| 902 |
+
health_status_json = gr.JSON(
|
| 903 |
+
label="Health Check Results (JSON)",
|
| 904 |
+
value={}
|
| 905 |
+
)
|
| 906 |
+
with gr.Column():
|
| 907 |
+
diagnostics_markdown = gr.Markdown(
|
| 908 |
+
value="Click refresh to load diagnostics",
|
| 909 |
+
label="Diagnostics Summary"
|
| 910 |
+
)
|
| 911 |
+
|
| 912 |
+
gr.Markdown("""
|
| 913 |
+
**Diagnostic Information Sources:**
|
| 914 |
+
- **Session Archive:** Agent state transitions, session archival events
|
| 915 |
+
- **Health Monitor:** Component health checks, system status, error tracking
|
| 916 |
+
|
| 917 |
+
**Common Error States:**
|
| 918 |
+
- **unknown error**: Check health monitor logs for component failures
|
| 919 |
+
- **RUNNING stage with errors**: Agent may be in recovery state
|
| 920 |
+
- **component unhealthy**: Specific component (brain, rbac, etc.) needs attention
|
| 921 |
+
""")
|
| 922 |
+
|
| 923 |
# Footer with refresh
|
| 924 |
with gr.Row():
|
| 925 |
refresh_btn = gr.Button("🔄 Refresh Dashboard", variant="primary")
|
|
|
|
| 937 |
]
|
| 938 |
)
|
| 939 |
|
| 940 |
+
# Diagnostics refresh handler (this references the button inside the tab)
|
| 941 |
+
# Note: This is defined after the tabs are created, so the button reference is available
|
| 942 |
+
|
| 943 |
# Manual refresh for main dashboard
|
| 944 |
refresh_btn.click(
|
| 945 |
fn=refresh_all,
|
|
|
|
| 979 |
outputs=[health_json, logs_table, health_report, last_updated]
|
| 980 |
)
|
| 981 |
|
| 982 |
+
# Diagnostics refresh handler
|
| 983 |
+
refresh_diagnostics_btn.click(
|
| 984 |
+
fn=refresh_diagnostics,
|
| 985 |
+
inputs=[],
|
| 986 |
+
outputs=[
|
| 987 |
+
session_logs_json,
|
| 988 |
+
health_status_json,
|
| 989 |
+
session_logs_table,
|
| 990 |
+
diagnostics_markdown,
|
| 991 |
+
diagnostics_last_updated
|
| 992 |
+
]
|
| 993 |
+
)
|
| 994 |
+
|
| 995 |
return app
|
| 996 |
|
| 997 |
|