openclaw-agent-dashboard 1.0.4

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 (111) hide show
  1. package/.github/workflows/release.yml +56 -0
  2. package/README.md +302 -0
  3. package/docs/CHANGELOG_AGENT_MODIFICATIONS.md +132 -0
  4. package/docs/RELEASE-LATEST.md +189 -0
  5. package/docs/RELEASE-MODEL-CONFIG.md +95 -0
  6. package/docs/release-guide.md +259 -0
  7. package/docs/release-operations-manual.md +167 -0
  8. package/docs/specs/tr3-install-system.md +580 -0
  9. package/docs/windows-collaboration-model-paths-troubleshooting.md +0 -0
  10. package/frontend/index.html +12 -0
  11. package/frontend/package-lock.json +1240 -0
  12. package/frontend/package.json +19 -0
  13. package/frontend/src/App.vue +331 -0
  14. package/frontend/src/components/AgentCard.vue +796 -0
  15. package/frontend/src/components/AgentConfigPanel.vue +539 -0
  16. package/frontend/src/components/AgentDetailPanel.vue +738 -0
  17. package/frontend/src/components/ErrorAnalysisView.vue +546 -0
  18. package/frontend/src/components/ErrorCenterPanel.vue +844 -0
  19. package/frontend/src/components/PerformanceMonitor.vue +515 -0
  20. package/frontend/src/components/SettingsPanel.vue +236 -0
  21. package/frontend/src/components/TokenAnalysisPanel.vue +683 -0
  22. package/frontend/src/components/chain/ChainEdge.vue +85 -0
  23. package/frontend/src/components/chain/ChainNode.vue +166 -0
  24. package/frontend/src/components/chain/TaskChainView.vue +425 -0
  25. package/frontend/src/components/chain/index.ts +3 -0
  26. package/frontend/src/components/chain/types.ts +70 -0
  27. package/frontend/src/components/collaboration/CollaborationFlowSection.vue +1032 -0
  28. package/frontend/src/components/collaboration/CollaborationFlowWrapper.vue +113 -0
  29. package/frontend/src/components/performance/PerformancePanel.vue +119 -0
  30. package/frontend/src/components/performance/PerformanceSection.vue +1137 -0
  31. package/frontend/src/components/tasks/TaskStatusSection.vue +973 -0
  32. package/frontend/src/components/timeline/TimelineConnector.vue +31 -0
  33. package/frontend/src/components/timeline/TimelineRound.vue +135 -0
  34. package/frontend/src/components/timeline/TimelineStep.vue +691 -0
  35. package/frontend/src/components/timeline/TimelineToolLink.vue +109 -0
  36. package/frontend/src/components/timeline/TimelineView.vue +540 -0
  37. package/frontend/src/components/timeline/index.ts +5 -0
  38. package/frontend/src/components/timeline/types.ts +120 -0
  39. package/frontend/src/composables/index.ts +7 -0
  40. package/frontend/src/composables/useDebounce.ts +48 -0
  41. package/frontend/src/composables/useRealtime.ts +52 -0
  42. package/frontend/src/composables/useState.ts +52 -0
  43. package/frontend/src/composables/useThrottle.ts +46 -0
  44. package/frontend/src/composables/useVirtualScroll.ts +106 -0
  45. package/frontend/src/main.ts +4 -0
  46. package/frontend/src/managers/EventDispatcher.ts +127 -0
  47. package/frontend/src/managers/RealtimeDataManager.ts +293 -0
  48. package/frontend/src/managers/StateManager.ts +128 -0
  49. package/frontend/src/managers/index.ts +5 -0
  50. package/frontend/src/types/collaboration.ts +135 -0
  51. package/frontend/src/types/index.ts +20 -0
  52. package/frontend/src/types/performance.ts +105 -0
  53. package/frontend/src/types/task.ts +38 -0
  54. package/frontend/vite.config.ts +18 -0
  55. package/package.json +22 -0
  56. package/plugin/README.md +99 -0
  57. package/plugin/config.json.example +1 -0
  58. package/plugin/index.js +250 -0
  59. package/plugin/openclaw.plugin.json +17 -0
  60. package/plugin/package.json +21 -0
  61. package/scripts/build-plugin.js +67 -0
  62. package/scripts/bundle.sh +62 -0
  63. package/scripts/install-plugin.sh +162 -0
  64. package/scripts/install-python-deps.js +346 -0
  65. package/scripts/install-python-deps.sh +226 -0
  66. package/scripts/install.js +512 -0
  67. package/scripts/install.sh +367 -0
  68. package/scripts/lib/common.js +490 -0
  69. package/scripts/lib/common.sh +137 -0
  70. package/scripts/release-pack.sh +110 -0
  71. package/scripts/start.js +50 -0
  72. package/scripts/test_available_models.py +284 -0
  73. package/scripts/test_websocket_ping.py +44 -0
  74. package/src/backend/agents.py +73 -0
  75. package/src/backend/api/__init__.py +1 -0
  76. package/src/backend/api/agent_config_api.py +90 -0
  77. package/src/backend/api/agents.py +73 -0
  78. package/src/backend/api/agents_config.py +75 -0
  79. package/src/backend/api/chains.py +126 -0
  80. package/src/backend/api/collaboration.py +902 -0
  81. package/src/backend/api/debug_paths.py +39 -0
  82. package/src/backend/api/error_analysis.py +146 -0
  83. package/src/backend/api/errors.py +281 -0
  84. package/src/backend/api/performance.py +784 -0
  85. package/src/backend/api/subagents.py +770 -0
  86. package/src/backend/api/timeline.py +144 -0
  87. package/src/backend/api/websocket.py +251 -0
  88. package/src/backend/collaboration.py +405 -0
  89. package/src/backend/data/__init__.py +1 -0
  90. package/src/backend/data/agent_config_manager.py +270 -0
  91. package/src/backend/data/chain_reader.py +299 -0
  92. package/src/backend/data/config_reader.py +153 -0
  93. package/src/backend/data/error_analyzer.py +430 -0
  94. package/src/backend/data/session_reader.py +445 -0
  95. package/src/backend/data/subagent_reader.py +244 -0
  96. package/src/backend/data/task_history.py +118 -0
  97. package/src/backend/data/timeline_reader.py +981 -0
  98. package/src/backend/errors.py +63 -0
  99. package/src/backend/main.py +89 -0
  100. package/src/backend/mechanism_reader.py +131 -0
  101. package/src/backend/mechanisms.py +32 -0
  102. package/src/backend/performance.py +474 -0
  103. package/src/backend/requirements.txt +5 -0
  104. package/src/backend/session_reader.py +238 -0
  105. package/src/backend/status/__init__.py +1 -0
  106. package/src/backend/status/error_detector.py +122 -0
  107. package/src/backend/status/status_calculator.py +301 -0
  108. package/src/backend/status_calculator.py +121 -0
  109. package/src/backend/subagent_reader.py +229 -0
  110. package/src/backend/watchers/__init__.py +4 -0
  111. package/src/backend/watchers/file_watcher.py +159 -0
@@ -0,0 +1,39 @@
1
+ """
2
+ 诊断接口 - 路径解析与关键文件检查
3
+ 用于 Windows 等跨机器环境排查协作流程、模型配置等问题
4
+ """
5
+ from fastapi import APIRouter
6
+
7
+ router = APIRouter()
8
+
9
+
10
+ @router.get("/debug/paths")
11
+ async def get_debug_paths():
12
+ """
13
+ 返回后端当前解析出的 OpenClaw 根目录及关键文件/目录存在性。
14
+ 用于跨机器环境(如 Windows)排查协作流程、时序视图、模型列表等问题。
15
+ """
16
+ try:
17
+ from data.config_reader import get_openclaw_root
18
+ root = get_openclaw_root()
19
+ except Exception as e:
20
+ return {
21
+ "success": False,
22
+ "error": str(e),
23
+ "openclawRoot": None,
24
+ "openclawJsonExists": False,
25
+ "agentsDirExists": False,
26
+ "subagentsRunsExists": False,
27
+ }
28
+
29
+ openclaw_json = root / "openclaw.json"
30
+ agents_dir = root / "agents"
31
+ subagents_runs = root / "subagents" / "runs.json"
32
+
33
+ return {
34
+ "success": True,
35
+ "openclawRoot": str(root),
36
+ "openclawJsonExists": openclaw_json.exists(),
37
+ "agentsDirExists": agents_dir.exists() and agents_dir.is_dir(),
38
+ "subagentsRunsExists": subagents_runs.exists(),
39
+ }
@@ -0,0 +1,146 @@
1
+ """
2
+ 错误分析 API - 提供错误根因分析接口
3
+ """
4
+ from fastapi import APIRouter, HTTPException
5
+ from typing import Optional
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ sys.path.append(str(Path(__file__).parent.parent))
10
+
11
+ from data.error_analyzer import (
12
+ analyze_agent_errors,
13
+ analyze_all_agents_errors,
14
+ get_error_detail,
15
+ classify_error,
16
+ get_error_suggestions,
17
+ )
18
+
19
+ router = APIRouter()
20
+
21
+
22
+ def format_error_for_display(error: dict) -> dict:
23
+ """格式化错误信息用于前端展示"""
24
+ severity_colors = {
25
+ 'critical': '#dc2626',
26
+ 'high': '#f97316',
27
+ 'medium': '#fbbf24',
28
+ 'low': '#6b7280',
29
+ }
30
+
31
+ severity_labels = {
32
+ 'critical': '严重',
33
+ 'high': '高',
34
+ 'medium': '中',
35
+ 'low': '低',
36
+ }
37
+
38
+ type_labels = {
39
+ 'api_auth': 'API 认证错误',
40
+ 'api_rate_limit': 'API 限流',
41
+ 'api_model': '模型错误',
42
+ 'timeout': '超时',
43
+ 'permission': '权限错误',
44
+ 'tool_error': '工具错误',
45
+ 'subagent': '子任务错误',
46
+ 'network': '网络错误',
47
+ 'unknown': '未知错误',
48
+ }
49
+
50
+ error_type = error.get('errorType', 'unknown')
51
+ severity = error.get('severity', 'medium')
52
+
53
+ return {
54
+ **error,
55
+ 'errorTypeLabel': type_labels.get(error_type, error_type),
56
+ 'severityLabel': severity_labels.get(severity, severity),
57
+ 'severityColor': severity_colors.get(severity, '#6b7280'),
58
+ }
59
+
60
+
61
+ @router.get("/error-analysis")
62
+ async def get_global_error_analysis():
63
+ """
64
+ 获取所有 Agent 的错误分析概览
65
+
66
+ 返回全局错误统计和每个 Agent 的错误摘要
67
+ """
68
+ try:
69
+ result = analyze_all_agents_errors()
70
+
71
+ # 格式化错误信息
72
+ for agent_result in result.get('agents', []):
73
+ agent_result['errors'] = [
74
+ format_error_for_display(e)
75
+ for e in agent_result.get('errors', [])
76
+ ]
77
+
78
+ return result
79
+ except Exception as e:
80
+ import traceback
81
+ traceback.print_exc()
82
+ return {
83
+ 'agents': [],
84
+ 'globalSummary': {},
85
+ 'error': str(e),
86
+ }
87
+
88
+
89
+ @router.get("/error-analysis/{agent_id}")
90
+ async def get_agent_error_analysis(agent_id: str, session_limit: int = 5):
91
+ """
92
+ 获取单个 Agent 的错误分析
93
+
94
+ - agent_id: Agent ID
95
+ - session_limit: 分析最近的 N 个 session
96
+ """
97
+ try:
98
+ result = analyze_agent_errors(agent_id, session_limit)
99
+ result['errors'] = [
100
+ format_error_for_display(e)
101
+ for e in result.get('errors', [])
102
+ ]
103
+ return result
104
+ except Exception as e:
105
+ import traceback
106
+ traceback.print_exc()
107
+ raise HTTPException(status_code=500, detail=str(e))
108
+
109
+
110
+ @router.get("/error-analysis/{agent_id}/{session_file}/{turn_index}")
111
+ async def get_error_detail_api(agent_id: str, session_file: str, turn_index: int):
112
+ """
113
+ 获取单个错误的详细信息
114
+
115
+ 包括错误发生前的工具调用链
116
+ """
117
+ try:
118
+ error = get_error_detail(agent_id, session_file, turn_index)
119
+ if not error:
120
+ raise HTTPException(status_code=404, detail="Error not found")
121
+ return format_error_for_display(error)
122
+ except HTTPException:
123
+ raise
124
+ except Exception as e:
125
+ raise HTTPException(status_code=500, detail=str(e))
126
+
127
+
128
+ @router.post("/error-analysis/classify")
129
+ async def classify_error_message(message: str):
130
+ """
131
+ 对给定的错误消息进行分类
132
+
133
+ 返回错误类型、严重程度和修复建议
134
+ """
135
+ try:
136
+ error_type, severity = classify_error(message)
137
+ suggestions = get_error_suggestions(error_type, message)
138
+
139
+ return {
140
+ 'message': message,
141
+ 'errorType': error_type.value,
142
+ 'severity': severity.value,
143
+ 'suggestions': suggestions,
144
+ }
145
+ except Exception as e:
146
+ raise HTTPException(status_code=500, detail=str(e))
@@ -0,0 +1,281 @@
1
+ """
2
+ 错误中心 API - 聚合 Session 错误、Model Failures、API 状态
3
+ 支持统计、筛选、趋势分析
4
+ """
5
+ from fastapi import APIRouter, Query
6
+ from typing import List, Dict, Any, Optional
7
+ from datetime import datetime, timedelta
8
+ import sys
9
+ from pathlib import Path
10
+ import time
11
+ from collections import defaultdict
12
+
13
+ sys.path.append(str(Path(__file__).parent.parent))
14
+
15
+ router = APIRouter()
16
+
17
+
18
+ # 错误类型映射
19
+ ERROR_TYPE_MAP = {
20
+ 'rate-limit': {'label': 'Rate Limit', 'color': '#f59e0b', 'severity': 'warning'},
21
+ 'token-limit': {'label': 'Token 超限', 'color': '#8b5cf6', 'severity': 'warning'},
22
+ 'timeout': {'label': '超时', 'color': '#ef4444', 'severity': 'error'},
23
+ 'auth': {'label': '认证失败', 'color': '#dc2626', 'severity': 'critical'},
24
+ 'unknown': {'label': '未知错误', 'color': '#6b7280', 'severity': 'error'},
25
+ }
26
+
27
+
28
+ def classify_error(err_msg: str) -> str:
29
+ """更精细的错误分类"""
30
+ err_lower = err_msg.lower()
31
+
32
+ if '429' in err_msg or 'rate limit' in err_lower or 'too many requests' in err_lower:
33
+ return 'rate-limit'
34
+ elif 'token' in err_lower or 'context' in err_lower or 'length' in err_lower:
35
+ return 'token-limit'
36
+ elif 'timeout' in err_lower or '超时' in err_msg or 'timed out' in err_lower:
37
+ return 'timeout'
38
+ elif '401' in err_msg or '403' in err_msg or 'auth' in err_lower or 'unauthorized' in err_lower:
39
+ return 'auth'
40
+ else:
41
+ return 'unknown'
42
+
43
+
44
+ def get_session_errors(limit: int = 100, agent_filter: str = None, type_filter: str = None) -> List[Dict]:
45
+ """获取 Session 错误"""
46
+ from data.session_reader import get_recent_messages
47
+ from data.config_reader import get_agents_list
48
+
49
+ errors = []
50
+ agents = get_agents_list()
51
+
52
+ for agent in agents:
53
+ agent_id = agent.get('id', '')
54
+ if not agent_id:
55
+ continue
56
+ if agent_filter and agent_id != agent_filter:
57
+ continue
58
+
59
+ messages = get_recent_messages(agent_id, limit=200)
60
+ for msg in messages:
61
+ if msg.get('stopReason') != 'error':
62
+ continue
63
+
64
+ err_msg = msg.get('errorMessage', '') or ''
65
+ err_type = classify_error(err_msg)
66
+
67
+ if type_filter and err_type != type_filter:
68
+ continue
69
+
70
+ errors.append({
71
+ "id": f"session-{agent_id}-{msg.get('timestamp', 0)}",
72
+ "source": "session",
73
+ "agentId": agent_id,
74
+ "type": err_type,
75
+ "typeLabel": ERROR_TYPE_MAP.get(err_type, {}).get('label', err_type),
76
+ "severity": ERROR_TYPE_MAP.get(err_type, {}).get('severity', 'error'),
77
+ "message": err_msg[:300] if err_msg else "(无详情)",
78
+ "fullMessage": err_msg,
79
+ "timestamp": msg.get('timestamp', 0),
80
+ "datetime": datetime.fromtimestamp(msg.get('timestamp', 0) / 1000).strftime('%Y-%m-%d %H:%M:%S') if msg.get('timestamp') else '-',
81
+ })
82
+
83
+ errors.sort(key=lambda x: x["timestamp"], reverse=True)
84
+ return errors[:limit]
85
+
86
+
87
+ def get_model_failures(limit: int = 100, model_filter: str = None, type_filter: str = None) -> List[Dict]:
88
+ """获取 Model Failures"""
89
+ from status.error_detector import parse_failure_log
90
+
91
+ errors = []
92
+ failures = parse_failure_log()
93
+
94
+ for f in failures:
95
+ model = f.get("model", "")
96
+ if model_filter and model != model_filter:
97
+ continue
98
+
99
+ err_type = f.get("error_type", "unknown")
100
+ if type_filter and err_type != type_filter:
101
+ continue
102
+
103
+ timestamp = f.get("timestamp", 0)
104
+ errors.append({
105
+ "id": f"model-{timestamp}-{model}",
106
+ "source": "model",
107
+ "model": model,
108
+ "type": err_type,
109
+ "typeLabel": ERROR_TYPE_MAP.get(err_type, {}).get('label', err_type),
110
+ "severity": ERROR_TYPE_MAP.get(err_type, {}).get('severity', 'error'),
111
+ "message": (f.get("message", "") or "")[:300],
112
+ "fullMessage": f.get("message", "") or "",
113
+ "timestamp": timestamp,
114
+ "datetime": datetime.fromtimestamp(timestamp / 1000).strftime('%Y-%m-%d %H:%M:%S') if timestamp else '-',
115
+ })
116
+
117
+ return errors[:limit]
118
+
119
+
120
+ def get_api_status() -> List[Dict]:
121
+ """获取 API 状态(整合自原 api_status 模块)"""
122
+ from status.error_detector import parse_failure_log
123
+
124
+ failures = parse_failure_log()
125
+ status_map = {}
126
+ now = int(time.time() * 1000)
127
+
128
+ for failure in failures:
129
+ model = failure.get('model', '')
130
+ if not model:
131
+ continue
132
+
133
+ if model not in status_map:
134
+ status_map[model] = {
135
+ 'model': model,
136
+ 'provider': parse_provider(model),
137
+ 'status': 'healthy',
138
+ 'errorCount': 0,
139
+ 'lastError': None,
140
+ }
141
+
142
+ status_map[model]['errorCount'] += 1
143
+
144
+ # 最近 10 分钟内有错误
145
+ if failure['timestamp'] > now - 600000:
146
+ if failure['timestamp'] > now - 120000: # 2分钟内
147
+ status_map[model]['status'] = 'down'
148
+ else:
149
+ status_map[model]['status'] = 'degraded'
150
+
151
+ if not status_map[model]['lastError']:
152
+ status_map[model]['lastError'] = {
153
+ 'type': failure.get('error_type', 'unknown'),
154
+ 'message': (failure.get('message', '') or '')[:100],
155
+ 'timestamp': failure['timestamp'],
156
+ }
157
+
158
+ return list(status_map.values())
159
+
160
+
161
+ def parse_provider(model: str) -> str:
162
+ """从模型名解析服务商"""
163
+ if model.startswith('glm'):
164
+ return 'zhipu'
165
+ elif model.startswith('qwen'):
166
+ return 'qwen'
167
+ elif model.startswith('kimi'):
168
+ return 'moonshot'
169
+ elif model.startswith('claude'):
170
+ return 'anthropic'
171
+ elif model.startswith('gpt'):
172
+ return 'openai'
173
+ else:
174
+ return 'unknown'
175
+
176
+
177
+ def get_error_stats(session_errors: List, model_failures: List) -> Dict:
178
+ """计算错误统计"""
179
+ # 按类型统计
180
+ type_stats = defaultdict(lambda: {'count': 0, 'label': '', 'color': ''})
181
+ for e in session_errors + model_failures:
182
+ err_type = e.get('type', 'unknown')
183
+ type_stats[err_type]['count'] += 1
184
+ type_stats[err_type]['label'] = ERROR_TYPE_MAP.get(err_type, {}).get('label', err_type)
185
+ type_stats[err_type]['color'] = ERROR_TYPE_MAP.get(err_type, {}).get('color', '#6b7280')
186
+
187
+ # 按 Agent 统计
188
+ agent_stats = defaultdict(lambda: {'count': 0, 'agentId': ''})
189
+ for e in session_errors:
190
+ agent_id = e.get('agentId', 'unknown')
191
+ agent_stats[agent_id]['count'] += 1
192
+ agent_stats[agent_id]['agentId'] = agent_id
193
+
194
+ # 按小时统计(最近 24 小时)
195
+ hourly_stats = defaultdict(int)
196
+ now = datetime.now()
197
+ for e in session_errors + model_failures:
198
+ ts = e.get('timestamp', 0)
199
+ if ts:
200
+ dt = datetime.fromtimestamp(ts / 1000)
201
+ if dt > now - timedelta(hours=24):
202
+ hour_key = dt.strftime('%Y-%m-%d %H:00')
203
+ hourly_stats[hour_key] += 1
204
+
205
+ # 生成完整的小时序列
206
+ hourly_list = []
207
+ for i in range(24):
208
+ hour = (now - timedelta(hours=23-i)).strftime('%Y-%m-%d %H:00')
209
+ hourly_list.append({
210
+ 'hour': hour,
211
+ 'count': hourly_stats.get(hour, 0)
212
+ })
213
+
214
+ return {
215
+ 'totalCount': len(session_errors) + len(model_failures),
216
+ 'sessionErrorCount': len(session_errors),
217
+ 'modelFailureCount': len(model_failures),
218
+ 'byType': dict(type_stats),
219
+ 'byAgent': dict(agent_stats),
220
+ 'hourlyTrend': hourly_list,
221
+ }
222
+
223
+
224
+ @router.get("/errors")
225
+ async def get_errors(
226
+ limit: int = Query(50, ge=1, le=500),
227
+ agent: Optional[str] = Query(None, description="按 Agent ID 筛选"),
228
+ type: Optional[str] = Query(None, description="按错误类型筛选"),
229
+ model: Optional[str] = Query(None, description="按模型筛选"),
230
+ ):
231
+ """
232
+ 获取错误中心数据
233
+ 支持按 Agent、类型、模型筛选
234
+ """
235
+ session_errors = get_session_errors(limit, agent, type)
236
+ model_failures = get_model_failures(limit, model, type)
237
+
238
+ return {
239
+ "sessionErrors": session_errors,
240
+ "modelFailures": model_failures,
241
+ }
242
+
243
+
244
+ @router.get("/errors/stats")
245
+ async def get_errors_stats():
246
+ """
247
+ 获取错误统计数据
248
+ 包括:总数、按类型分布、按 Agent 分布、时间趋势
249
+ """
250
+ session_errors = get_session_errors(200)
251
+ model_failures = get_model_failures(200)
252
+
253
+ return get_error_stats(session_errors, model_failures)
254
+
255
+
256
+ @router.get("/errors/api-status")
257
+ async def get_errors_api_status():
258
+ """
259
+ 获取 API 状态
260
+ 整合自原 api_status 模块
261
+ """
262
+ return get_api_status()
263
+
264
+
265
+ @router.get("/errors/summary")
266
+ async def get_errors_summary():
267
+ """
268
+ 获取错误中心完整数据(一次请求获取所有)
269
+ 包括:错误列表、统计、API 状态
270
+ """
271
+ session_errors = get_session_errors(100)
272
+ model_failures = get_model_failures(100)
273
+ api_status = get_api_status()
274
+ stats = get_error_stats(session_errors, model_failures)
275
+
276
+ return {
277
+ "sessionErrors": session_errors,
278
+ "modelFailures": model_failures,
279
+ "apiStatus": api_status,
280
+ "stats": stats,
281
+ }