openclaw-agent-dashboard 1.0.46 → 1.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dashboard/api/agents.py +29 -29
  2. package/dashboard/api/metrics.py +24 -0
  3. package/dashboard/api/websocket.py +445 -194
  4. package/dashboard/core/agent_state_ingestor.py +474 -0
  5. package/dashboard/core/checkpoint_manager.py +383 -0
  6. package/dashboard/core/config_fortify.py +33 -0
  7. package/dashboard/core/event_bus.py +128 -0
  8. package/dashboard/core/event_types.py +169 -0
  9. package/dashboard/core/file_change_classifier.py +80 -0
  10. package/dashboard/core/metrics_collector.py +106 -0
  11. package/dashboard/core/state_store.py +112 -0
  12. package/dashboard/data/session_reader.py +94 -0
  13. package/dashboard/ingest/collaboration_ingestor.py +176 -0
  14. package/dashboard/ingest/performance_ingestor.py +134 -0
  15. package/dashboard/ingest/task_ingestor.py +164 -0
  16. package/dashboard/main.py +54 -2
  17. package/dashboard/status/change_tracker.py +1 -1
  18. package/dashboard/status/status_calculator.py +129 -53
  19. package/dashboard/tests/ecs/__init__.py +0 -0
  20. package/dashboard/tests/ecs/conftest.py +150 -0
  21. package/dashboard/tests/ecs/test_agent_state_ingestor.py +430 -0
  22. package/dashboard/tests/ecs/test_c0_acceptance.py +342 -0
  23. package/dashboard/tests/ecs/test_c1_integration.py +804 -0
  24. package/dashboard/tests/ecs/test_c2_integration.py +611 -0
  25. package/dashboard/tests/ecs/test_checkpoint_manager.py +495 -0
  26. package/dashboard/tests/ecs/test_event_bus.py +236 -0
  27. package/dashboard/tests/ecs/test_file_change_classifier.py +244 -0
  28. package/dashboard/tests/ecs/test_file_watcher_c0.py +173 -0
  29. package/dashboard/tests/ecs/test_integration.py +767 -0
  30. package/dashboard/tests/ecs/test_main_lifespan.py +49 -0
  31. package/dashboard/tests/ecs/test_metrics_collector.py +202 -0
  32. package/dashboard/tests/ecs/test_state_store.py +295 -0
  33. package/dashboard/tests/ecs/test_websocket_c0.py +301 -0
  34. package/dashboard/tests/test_bench_fortify.py +21 -5
  35. package/dashboard/tests/test_event_bus.py +195 -0
  36. package/dashboard/tests/test_file_change_classifier.py +94 -0
  37. package/dashboard/tests/test_metrics_collector.py +130 -0
  38. package/dashboard/tests/test_session_reader_tail.py +48 -0
  39. package/dashboard/tests/test_state_store.py +176 -0
  40. package/dashboard/watchers/file_watcher.py +32 -17
  41. package/frontend-dist/assets/{index-CKnEqyAW.css → index-B5BS7sok.css} +1 -1
  42. package/frontend-dist/assets/index-qkRGxSvS.js +24 -0
  43. package/frontend-dist/index.html +2 -2
  44. package/openclaw.plugin.json +1 -1
  45. package/package.json +1 -1
  46. package/frontend-dist/assets/index-B9FVSu__.js +0 -24
@@ -13,11 +13,6 @@ from pydantic import BaseModel
13
13
 
14
14
  from api.input_safety import require_safe_agent_id
15
15
 
16
- from status.status_calculator import (
17
- get_agents_with_status,
18
- format_last_active
19
- )
20
-
21
16
  router = APIRouter()
22
17
 
23
18
 
@@ -32,26 +27,40 @@ class AgentStatus(BaseModel):
32
27
  error: Optional[dict] = None
33
28
 
34
29
 
30
+ async def _load_agents_with_retry(max_retry: int = 2) -> list:
31
+ """C1: Load agents with retry. get_agents_with_status is now truly async."""
32
+ from core.error_handler import ErrorHandler
33
+ from status.status_calculator import get_agents_with_status
34
+
35
+ for attempt in range(max_retry + 1):
36
+ try:
37
+ return await get_agents_with_status()
38
+ except OSError as e:
39
+ from core.error_handler import classify_exception, record_error
40
+ cat = classify_exception(e)
41
+ record_error(cat, str(e), f"get_agents_with_status:attempt={attempt}", exc=e)
42
+ if attempt < max_retry:
43
+ import time
44
+ time.sleep(0.5 * (attempt + 1))
45
+ else:
46
+ from core.fallback_manager import run_fallback
47
+ fb = run_fallback(cat)
48
+ return fb if fb else []
49
+ return []
50
+
51
+
35
52
  @router.get("/agents", response_model=List[AgentStatus])
36
53
  async def get_agents():
37
54
  """获取所有 Agent 列表及状态"""
38
- from core.error_handler import ErrorHandler
55
+ from status.status_calculator import format_last_active
39
56
 
40
- def _load():
41
- h = ErrorHandler(max_retry=2, base_delay=0.5)
42
- return h.run_with_retry(
43
- lambda: get_agents_with_status(),
44
- operation="get_agents_with_status",
45
- error_type="io-error",
46
- )
57
+ agents = await _load_agents_with_retry()
47
58
 
48
- agents = await asyncio.to_thread(_load)
49
-
50
59
  # 格式化最后活跃时间
51
60
  for agent in agents:
52
61
  if agent.get('lastActiveAt'):
53
62
  agent['lastActiveFormatted'] = format_last_active(agent['lastActiveAt'])
54
-
63
+
55
64
  return agents
56
65
 
57
66
 
@@ -59,26 +68,17 @@ async def get_agents():
59
68
  async def get_agent(agent_id: str):
60
69
  """获取单个 Agent 详情"""
61
70
  require_safe_agent_id(agent_id)
62
- from core.error_handler import ErrorHandler
63
-
64
- def _load():
65
- h = ErrorHandler(max_retry=2, base_delay=0.5)
66
- return h.run_with_retry(
67
- lambda: get_agents_with_status(),
68
- operation="get_agents_with_status",
69
- error_type="io-error",
70
- )
71
-
72
- agents = await asyncio.to_thread(_load)
73
-
71
+ from status.status_calculator import format_last_active
74
72
  from data.config_reader import agent_ids_equal
75
73
 
74
+ agents = await _load_agents_with_retry()
75
+
76
76
  for agent in agents:
77
77
  if agent_ids_equal(agent['id'], agent_id):
78
78
  if agent.get('lastActiveAt'):
79
79
  agent['lastActiveFormatted'] = format_last_active(agent['lastActiveAt'])
80
80
  return agent
81
-
81
+
82
82
  raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found")
83
83
 
84
84
 
@@ -0,0 +1,24 @@
1
+ """
2
+ ECS Metrics API — Prometheus-style metrics endpoint for C0 observability.
3
+
4
+ Exposes /api/metrics returning MetricsCollector snapshot.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from fastapi import APIRouter
9
+ from typing import Dict, Any
10
+
11
+ router = APIRouter()
12
+
13
+
14
+ @router.get("/metrics")
15
+ async def get_metrics():
16
+ """Return C0 runtime metrics (JSON format).
17
+
18
+ Prometheus text format is deferred to C1. C0 uses JSON for simplicity.
19
+ """
20
+ try:
21
+ from core.metrics_collector import get_metrics
22
+ return get_metrics().get_snapshot()
23
+ except Exception as e:
24
+ return {"error": str(e)}