openclaw-agent-dashboard 1.0.39 → 1.0.41
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.
- package/dashboard/api/agent_config_api.py +28 -7
- package/dashboard/api/agents.py +48 -10
- package/dashboard/api/agents_config.py +5 -1
- package/dashboard/api/chains.py +25 -5
- package/dashboard/api/collaboration.py +10 -9
- package/dashboard/api/debug_paths.py +5 -1
- package/dashboard/api/error_analysis.py +29 -11
- package/dashboard/api/errors.py +37 -11
- package/dashboard/api/fortify_routes.py +108 -0
- package/dashboard/api/input_safety.py +60 -0
- package/dashboard/api/performance.py +73 -53
- package/dashboard/api/subagents.py +95 -99
- package/dashboard/api/timeline.py +24 -3
- package/dashboard/api/version.py +2 -0
- package/dashboard/api/websocket.py +9 -7
- package/dashboard/core/__init__.py +1 -0
- package/dashboard/core/config_fortify.py +125 -0
- package/dashboard/core/error_handler.py +488 -0
- package/dashboard/core/fallback_manager.py +81 -0
- package/dashboard/core/logging_config.py +217 -0
- package/dashboard/core/safe_api_error.py +76 -0
- package/dashboard/core/schemas/__init__.py +16 -0
- package/dashboard/core/schemas/base.py +43 -0
- package/dashboard/core/schemas/session_schema.py +40 -0
- package/dashboard/core/schemas/subagent_schema.py +23 -0
- package/dashboard/data/agent_config_manager.py +6 -4
- package/dashboard/data/chain_reader.py +16 -12
- package/dashboard/data/error_analyzer.py +15 -11
- package/dashboard/data/session_reader.py +268 -46
- package/dashboard/data/subagent_reader.py +74 -49
- package/dashboard/data/timeline_reader.py +35 -49
- package/dashboard/main.py +24 -2
- package/dashboard/mechanism_reader.py +4 -5
- package/dashboard/mechanisms.py +2 -2
- package/dashboard/pytest.ini +3 -0
- package/dashboard/requirements.txt +5 -0
- package/dashboard/status/cache_fp_probe.py +40 -0
- package/dashboard/status/status_cache.py +199 -72
- package/dashboard/status/status_calculator.py +50 -30
- package/dashboard/tests/conftest.py +87 -0
- package/dashboard/tests/test_api_contracts.py +372 -0
- package/dashboard/tests/test_bench_fortify.py +176 -0
- package/dashboard/tests/test_fortify.py +952 -0
- package/dashboard/utils/__init__.py +1 -0
- package/dashboard/utils/data_repair.py +210 -0
- package/dashboard/watchers/file_watcher.py +380 -77
- package/frontend-dist/assets/{index-cYIOn3Wq.css → index-BIZ2xHfw.css} +1 -1
- package/frontend-dist/assets/{index-DyRXGevD.js → index-Cnr0b02R.js} +1 -1
- package/frontend-dist/index.html +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dashboard/agents.py +0 -74
- package/dashboard/collaboration.py +0 -407
- package/dashboard/errors.py +0 -63
- package/dashboard/performance.py +0 -474
- package/dashboard/session_reader.py +0 -240
- package/dashboard/status_calculator.py +0 -121
- package/dashboard/subagent_reader.py +0 -232
package/dashboard/agents.py
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Agent API 路由
|
|
3
|
-
"""
|
|
4
|
-
from fastapi import APIRouter
|
|
5
|
-
from pydantic import BaseModel
|
|
6
|
-
from typing import List, Optional
|
|
7
|
-
import sys
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
sys.path.append(str(Path(__file__).parent.parent))
|
|
10
|
-
|
|
11
|
-
from status.status_calculator import (
|
|
12
|
-
get_agents_with_status,
|
|
13
|
-
format_last_active
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
router = APIRouter()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class AgentStatus(BaseModel):
|
|
20
|
-
id: str
|
|
21
|
-
name: str
|
|
22
|
-
role: str
|
|
23
|
-
status: str # idle/working/down
|
|
24
|
-
currentTask: Optional[str] = None
|
|
25
|
-
lastActiveAt: Optional[int] = None
|
|
26
|
-
lastActiveFormatted: Optional[str] = None
|
|
27
|
-
error: Optional[dict] = None
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@router.get("/agents", response_model=List[AgentStatus])
|
|
31
|
-
async def get_agents():
|
|
32
|
-
"""获取所有 Agent 列表及状态"""
|
|
33
|
-
agents = get_agents_with_status()
|
|
34
|
-
|
|
35
|
-
# 格式化最后活跃时间
|
|
36
|
-
for agent in agents:
|
|
37
|
-
if agent.get('lastActiveAt'):
|
|
38
|
-
agent['lastActiveFormatted'] = format_last_active(agent['lastActiveAt'])
|
|
39
|
-
|
|
40
|
-
return agents
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@router.get("/agents/{agent_id}", response_model=AgentStatus)
|
|
44
|
-
async def get_agent(agent_id: str):
|
|
45
|
-
"""获取单个 Agent 详情"""
|
|
46
|
-
agents = get_agents_with_status()
|
|
47
|
-
|
|
48
|
-
from data.config_reader import agent_ids_equal
|
|
49
|
-
|
|
50
|
-
for agent in agents:
|
|
51
|
-
if agent_ids_equal(agent['id'], agent_id):
|
|
52
|
-
if agent.get('lastActiveAt'):
|
|
53
|
-
agent['lastActiveFormatted'] = format_last_active(agent['lastActiveAt'])
|
|
54
|
-
return agent
|
|
55
|
-
|
|
56
|
-
from fastapi import HTTPException
|
|
57
|
-
raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found")
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@router.get("/agents/{agent_id}/output")
|
|
61
|
-
async def get_agent_output(agent_id: str, limit: int = 50):
|
|
62
|
-
"""
|
|
63
|
-
获取 Agent 最近会话详情:每轮 user/assistant/toolResult 及 usage
|
|
64
|
-
用于调试视图展示
|
|
65
|
-
"""
|
|
66
|
-
from data.session_reader import get_session_turns
|
|
67
|
-
from data.config_reader import get_agent_config
|
|
68
|
-
|
|
69
|
-
if not get_agent_config(agent_id):
|
|
70
|
-
from fastapi import HTTPException
|
|
71
|
-
raise HTTPException(status_code=404, detail=f"Agent {agent_id} not found")
|
|
72
|
-
|
|
73
|
-
turns = get_session_turns(agent_id, limit=limit)
|
|
74
|
-
return {"agentId": agent_id, "turns": turns}
|
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
协作流程 API 路由
|
|
3
|
-
符合 PRD: 展示老 K 与所有子 Agents 之间的连接关系,任务从老 K 流向子 Agents
|
|
4
|
-
扩展: Agent 模型配置、模型节点、最近调用(光球展示)
|
|
5
|
-
"""
|
|
6
|
-
from fastapi import APIRouter
|
|
7
|
-
from pydantic import BaseModel
|
|
8
|
-
from typing import List, Optional, Dict, Any
|
|
9
|
-
import sys
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from datetime import datetime, timedelta, timezone
|
|
12
|
-
from zoneinfo import ZoneInfo
|
|
13
|
-
|
|
14
|
-
TZ_DISPLAY = ZoneInfo('Asia/Shanghai')
|
|
15
|
-
|
|
16
|
-
sys.path.append(str(Path(__file__).parent.parent))
|
|
17
|
-
|
|
18
|
-
router = APIRouter()
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class CollaborationNode(BaseModel):
|
|
22
|
-
id: str
|
|
23
|
-
type: str # agent/task/tool/model
|
|
24
|
-
name: str
|
|
25
|
-
status: str # idle/working/error (Agent 状态: 空闲/工作中/异常)
|
|
26
|
-
timestamp: Optional[int] = None
|
|
27
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class CollaborationEdge(BaseModel):
|
|
31
|
-
id: str
|
|
32
|
-
source: str
|
|
33
|
-
target: str
|
|
34
|
-
type: str # delegates/calls/returns/error/model
|
|
35
|
-
label: Optional[str] = None
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
class ModelCall(BaseModel):
|
|
39
|
-
id: str
|
|
40
|
-
agentId: str
|
|
41
|
-
model: str # provider/model
|
|
42
|
-
sessionId: str
|
|
43
|
-
trigger: str
|
|
44
|
-
tokens: int
|
|
45
|
-
timestamp: int # ms
|
|
46
|
-
time: str # HH:MM:SS
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class CollaborationFlow(BaseModel):
|
|
50
|
-
nodes: List[CollaborationNode]
|
|
51
|
-
edges: List[CollaborationEdge]
|
|
52
|
-
activePath: List[str]
|
|
53
|
-
lastUpdate: int
|
|
54
|
-
mainAgentId: Optional[str] = None # 主 Agent ID,前端用于布局与样式
|
|
55
|
-
agentModels: Optional[Dict[str, Dict[str, Any]]] = None
|
|
56
|
-
models: Optional[List[str]] = None
|
|
57
|
-
recentCalls: Optional[List[ModelCall]] = None
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def _parse_agent_id(session_key: str) -> str:
|
|
61
|
-
"""从 sessionKey (childSessionKey 或 requesterSessionKey) 解析 agentId"""
|
|
62
|
-
parts = (session_key or '').split(':')
|
|
63
|
-
if len(parts) >= 2 and parts[0] == 'agent':
|
|
64
|
-
return parts[1]
|
|
65
|
-
return ''
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _get_recent_model_calls(minutes: int = 30) -> List[Dict]:
|
|
69
|
-
"""获取最近 N 分钟的模型调用记录(用于光球展示)"""
|
|
70
|
-
from api.performance import parse_session_file_with_details
|
|
71
|
-
|
|
72
|
-
records = []
|
|
73
|
-
from data.config_reader import get_openclaw_root
|
|
74
|
-
openclaw_path = get_openclaw_root()
|
|
75
|
-
agents_path = openclaw_path / 'agents'
|
|
76
|
-
if not agents_path.exists():
|
|
77
|
-
return []
|
|
78
|
-
|
|
79
|
-
now = datetime.now(timezone.utc)
|
|
80
|
-
since = now - timedelta(minutes=minutes)
|
|
81
|
-
|
|
82
|
-
for agent_dir in agents_path.iterdir():
|
|
83
|
-
if not agent_dir.is_dir():
|
|
84
|
-
continue
|
|
85
|
-
agent_id = agent_dir.name
|
|
86
|
-
sessions_path = agent_dir / 'sessions'
|
|
87
|
-
if not sessions_path.exists():
|
|
88
|
-
continue
|
|
89
|
-
for session_file in sessions_path.glob('*.jsonl'):
|
|
90
|
-
if 'lock' in session_file.name or 'deleted' in session_file.name:
|
|
91
|
-
continue
|
|
92
|
-
for r in parse_session_file_with_details(session_file, agent_id):
|
|
93
|
-
if r['timestamp'] >= since:
|
|
94
|
-
ts = r['timestamp']
|
|
95
|
-
if ts.tzinfo is None:
|
|
96
|
-
ts = ts.replace(tzinfo=timezone.utc)
|
|
97
|
-
ts_local = ts.astimezone(TZ_DISPLAY)
|
|
98
|
-
records.append({
|
|
99
|
-
'agentId': agent_id,
|
|
100
|
-
'model': r.get('model', ''),
|
|
101
|
-
'sessionId': r['sessionId'],
|
|
102
|
-
'trigger': r.get('trigger', ''),
|
|
103
|
-
'tokens': r.get('tokens', 0),
|
|
104
|
-
'timestamp': int(ts.timestamp() * 1000),
|
|
105
|
-
'time': ts_local.strftime('%H:%M:%S')
|
|
106
|
-
})
|
|
107
|
-
records.sort(key=lambda x: x['timestamp'])
|
|
108
|
-
return records[-100:] # 最多 100 条
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
@router.get("/collaboration", response_model=CollaborationFlow)
|
|
112
|
-
async def get_collaboration():
|
|
113
|
-
"""获取协作流程数据 - 主 Agent 与子 Agents 的拓扑关系,含模型配置与最近调用"""
|
|
114
|
-
from data.config_reader import (
|
|
115
|
-
get_agents_list, get_agent_models, get_models_configured_by_agents,
|
|
116
|
-
get_model_display_name, get_main_agent_id, agent_ids_equal,
|
|
117
|
-
)
|
|
118
|
-
from data.subagent_reader import get_active_runs
|
|
119
|
-
from status.status_calculator import calculate_agent_status
|
|
120
|
-
|
|
121
|
-
nodes = []
|
|
122
|
-
edges = []
|
|
123
|
-
active_path = []
|
|
124
|
-
agent_models: Dict[str, Dict[str, Any]] = {}
|
|
125
|
-
models_list: List[str] = []
|
|
126
|
-
recent_calls: List[Dict] = []
|
|
127
|
-
|
|
128
|
-
main_agent_id = 'main'
|
|
129
|
-
try:
|
|
130
|
-
main_agent_id = get_main_agent_id()
|
|
131
|
-
agents_list = get_agents_list()
|
|
132
|
-
active_runs = get_active_runs()
|
|
133
|
-
|
|
134
|
-
main_agent_id = get_main_agent_id()
|
|
135
|
-
main_agent_config = next(
|
|
136
|
-
(a for a in agents_list if agent_ids_equal(a.get('id'), main_agent_id)), None
|
|
137
|
-
)
|
|
138
|
-
sub_agents_config = [a for a in agents_list if not agent_ids_equal(a.get('id'), main_agent_id)]
|
|
139
|
-
|
|
140
|
-
all_agents = [a for a in agents_list if a.get('id')]
|
|
141
|
-
for agent in all_agents:
|
|
142
|
-
aid = agent.get('id', '')
|
|
143
|
-
agent_models[aid] = get_agent_models(aid)
|
|
144
|
-
models_list = get_models_configured_by_agents()
|
|
145
|
-
recent_calls = _get_recent_model_calls(30)
|
|
146
|
-
|
|
147
|
-
main_display_name = (main_agent_config.get('name') if main_agent_config else None) or "主 Agent"
|
|
148
|
-
main_status = "working" if active_runs else "idle"
|
|
149
|
-
main_agent = CollaborationNode(
|
|
150
|
-
id=main_agent_id,
|
|
151
|
-
type="agent",
|
|
152
|
-
name=main_display_name,
|
|
153
|
-
status=main_status,
|
|
154
|
-
timestamp=int(__import__('time').time() * 1000),
|
|
155
|
-
metadata=agent_models.get(main_agent_id)
|
|
156
|
-
)
|
|
157
|
-
nodes.append(main_agent)
|
|
158
|
-
|
|
159
|
-
for agent in sub_agents_config:
|
|
160
|
-
agent_id = agent.get('id', '')
|
|
161
|
-
agent_name = agent.get('name', agent_id)
|
|
162
|
-
|
|
163
|
-
status = calculate_agent_status(agent_id)
|
|
164
|
-
if status == 'down':
|
|
165
|
-
status = 'error'
|
|
166
|
-
elif status == 'working':
|
|
167
|
-
status = 'working'
|
|
168
|
-
else:
|
|
169
|
-
status = 'idle'
|
|
170
|
-
|
|
171
|
-
sub_node = CollaborationNode(
|
|
172
|
-
id=agent_id,
|
|
173
|
-
type="agent",
|
|
174
|
-
name=agent_name,
|
|
175
|
-
status=status,
|
|
176
|
-
timestamp=None,
|
|
177
|
-
metadata=agent_models.get(agent_id)
|
|
178
|
-
)
|
|
179
|
-
nodes.append(sub_node)
|
|
180
|
-
|
|
181
|
-
edges.append(CollaborationEdge(
|
|
182
|
-
id=f"edge-{main_agent_id}-{agent_id}",
|
|
183
|
-
source=main_agent_id,
|
|
184
|
-
target=agent_id,
|
|
185
|
-
type="delegates",
|
|
186
|
-
label="委托"
|
|
187
|
-
))
|
|
188
|
-
|
|
189
|
-
# 3. 模型节点(右侧)
|
|
190
|
-
for i, model_id in enumerate(models_list):
|
|
191
|
-
model_node = CollaborationNode(
|
|
192
|
-
id=f"model-{model_id.replace('/', '-')}",
|
|
193
|
-
type="model",
|
|
194
|
-
name=get_model_display_name(model_id),
|
|
195
|
-
status="idle",
|
|
196
|
-
timestamp=None,
|
|
197
|
-
metadata={"modelId": model_id}
|
|
198
|
-
)
|
|
199
|
-
nodes.append(model_node)
|
|
200
|
-
|
|
201
|
-
# 4. Agent -> Model 边(配置了该模型的 Agent)
|
|
202
|
-
for agent in all_agents:
|
|
203
|
-
aid = agent.get('id', '')
|
|
204
|
-
cfg = agent_models.get(aid, {})
|
|
205
|
-
primary = cfg.get('primary', '')
|
|
206
|
-
fallbacks = cfg.get('fallbacks', [])
|
|
207
|
-
all_models = [primary] + [f for f in fallbacks if f != primary]
|
|
208
|
-
for mid in all_models:
|
|
209
|
-
if mid and mid in models_list:
|
|
210
|
-
mid_safe = mid.replace('/', '-')
|
|
211
|
-
edges.append(CollaborationEdge(
|
|
212
|
-
id=f"edge-{aid}-model-{mid_safe}",
|
|
213
|
-
source=aid,
|
|
214
|
-
target=f"model-{mid_safe}",
|
|
215
|
-
type="model",
|
|
216
|
-
label=mid
|
|
217
|
-
))
|
|
218
|
-
|
|
219
|
-
# 5. 活跃任务与 spawn 链:requesterSessionKey -> childSessionKey -> task
|
|
220
|
-
for run in active_runs[:10]:
|
|
221
|
-
child_key = run.get('childSessionKey', '')
|
|
222
|
-
requester_key = run.get('requesterSessionKey', '')
|
|
223
|
-
agent_id = _parse_agent_id(child_key)
|
|
224
|
-
requester_id = _parse_agent_id(requester_key)
|
|
225
|
-
if not agent_id:
|
|
226
|
-
continue
|
|
227
|
-
|
|
228
|
-
task_name = run.get('task', 'Unknown Task')
|
|
229
|
-
first_line = task_name.split('\n')[0].strip() if task_name else 'Unknown Task'
|
|
230
|
-
task_name = first_line if first_line else task_name
|
|
231
|
-
|
|
232
|
-
task_id = f"task-{run.get('runId', agent_id)}"
|
|
233
|
-
task_node = CollaborationNode(
|
|
234
|
-
id=task_id,
|
|
235
|
-
type="task",
|
|
236
|
-
name=task_name,
|
|
237
|
-
status="working",
|
|
238
|
-
timestamp=run.get('startedAt')
|
|
239
|
-
)
|
|
240
|
-
nodes.append(task_node)
|
|
241
|
-
|
|
242
|
-
edges.append(CollaborationEdge(
|
|
243
|
-
id=f"edge-{agent_id}-{task_id}",
|
|
244
|
-
source=agent_id,
|
|
245
|
-
target=task_id,
|
|
246
|
-
type="calls",
|
|
247
|
-
label="执行"
|
|
248
|
-
))
|
|
249
|
-
# Spawn 链:主 Agent 派发 -> 子 Agent 执行
|
|
250
|
-
if requester_id and requester_id != agent_id:
|
|
251
|
-
edges.append(CollaborationEdge(
|
|
252
|
-
id=f"edge-spawn-{requester_id}-{task_id}",
|
|
253
|
-
source=requester_id,
|
|
254
|
-
target=task_id,
|
|
255
|
-
type="delegates",
|
|
256
|
-
label="派发"
|
|
257
|
-
))
|
|
258
|
-
|
|
259
|
-
active_path.extend([main_agent_id, agent_id, task_id])
|
|
260
|
-
|
|
261
|
-
except Exception as e:
|
|
262
|
-
print(f"Error building collaboration data: {e}")
|
|
263
|
-
import traceback
|
|
264
|
-
traceback.print_exc()
|
|
265
|
-
|
|
266
|
-
if not nodes:
|
|
267
|
-
try:
|
|
268
|
-
main_agent_id = get_main_agent_id()
|
|
269
|
-
except Exception:
|
|
270
|
-
main_agent_id = 'main'
|
|
271
|
-
nodes = [
|
|
272
|
-
CollaborationNode(id=main_agent_id, type="agent", name="主 Agent", status="idle"),
|
|
273
|
-
]
|
|
274
|
-
|
|
275
|
-
# 构建 recentCalls 带 id
|
|
276
|
-
model_calls = [
|
|
277
|
-
ModelCall(
|
|
278
|
-
id=f"call-{i}",
|
|
279
|
-
agentId=r["agentId"],
|
|
280
|
-
model=r.get("model", ""),
|
|
281
|
-
sessionId=r.get("sessionId", ""),
|
|
282
|
-
trigger=r.get("trigger", ""),
|
|
283
|
-
tokens=r.get("tokens", 0),
|
|
284
|
-
timestamp=r.get("timestamp", 0),
|
|
285
|
-
time=r.get("time", "")
|
|
286
|
-
)
|
|
287
|
-
for i, r in enumerate(recent_calls)
|
|
288
|
-
]
|
|
289
|
-
|
|
290
|
-
return CollaborationFlow(
|
|
291
|
-
nodes=nodes,
|
|
292
|
-
edges=edges,
|
|
293
|
-
activePath=list(set(active_path)),
|
|
294
|
-
lastUpdate=int(__import__('time').time() * 1000),
|
|
295
|
-
mainAgentId=main_agent_id,
|
|
296
|
-
agentModels=agent_models,
|
|
297
|
-
models=models_list,
|
|
298
|
-
recentCalls=model_calls
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
class CollaborationDynamic(BaseModel):
|
|
303
|
-
"""仅动态数据:状态、小球、任务节点,不包含静态拓扑"""
|
|
304
|
-
activePath: List[str]
|
|
305
|
-
recentCalls: List[ModelCall]
|
|
306
|
-
agentStatuses: Dict[str, str] # agentId -> idle/working/error
|
|
307
|
-
taskNodes: List[CollaborationNode]
|
|
308
|
-
taskEdges: List[CollaborationEdge]
|
|
309
|
-
mainAgentId: str
|
|
310
|
-
lastUpdate: int
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
@router.get("/collaboration/dynamic", response_model=CollaborationDynamic)
|
|
314
|
-
async def get_collaboration_dynamic():
|
|
315
|
-
"""轻量接口:仅返回状态、小球、任务等动态数据,用于静默刷新,不触发整体重载"""
|
|
316
|
-
from data.config_reader import get_agents_list, get_main_agent_id
|
|
317
|
-
from data.subagent_reader import get_active_runs
|
|
318
|
-
from status.status_calculator import calculate_agent_status
|
|
319
|
-
|
|
320
|
-
active_path = []
|
|
321
|
-
agent_statuses: Dict[str, str] = {}
|
|
322
|
-
task_nodes: List[CollaborationNode] = []
|
|
323
|
-
task_edges: List[CollaborationEdge] = []
|
|
324
|
-
main_agent_id = 'main'
|
|
325
|
-
|
|
326
|
-
try:
|
|
327
|
-
main_agent_id = get_main_agent_id()
|
|
328
|
-
agents_list = get_agents_list()
|
|
329
|
-
active_runs = get_active_runs()
|
|
330
|
-
|
|
331
|
-
for agent in agents_list:
|
|
332
|
-
aid = agent.get('id', '')
|
|
333
|
-
if not aid:
|
|
334
|
-
continue
|
|
335
|
-
status = calculate_agent_status(aid)
|
|
336
|
-
if status == 'down':
|
|
337
|
-
status = 'error'
|
|
338
|
-
elif status == 'working':
|
|
339
|
-
status = 'working'
|
|
340
|
-
else:
|
|
341
|
-
status = 'idle'
|
|
342
|
-
agent_statuses[aid] = status
|
|
343
|
-
|
|
344
|
-
main_status = "working" if active_runs else "idle"
|
|
345
|
-
agent_statuses[main_agent_id] = main_status
|
|
346
|
-
|
|
347
|
-
for run in active_runs[:10]:
|
|
348
|
-
child_key = run.get('childSessionKey', '')
|
|
349
|
-
requester_key = run.get('requesterSessionKey', '')
|
|
350
|
-
agent_id = _parse_agent_id(child_key)
|
|
351
|
-
requester_id = _parse_agent_id(requester_key)
|
|
352
|
-
if not agent_id:
|
|
353
|
-
continue
|
|
354
|
-
task_name = run.get('task', 'Unknown Task')
|
|
355
|
-
first_line = task_name.split('\n')[0].strip() if task_name else 'Unknown Task'
|
|
356
|
-
task_name = first_line if first_line else task_name
|
|
357
|
-
task_id = f"task-{run.get('runId', agent_id)}"
|
|
358
|
-
task_nodes.append(CollaborationNode(
|
|
359
|
-
id=task_id,
|
|
360
|
-
type="task",
|
|
361
|
-
name=task_name,
|
|
362
|
-
status="working",
|
|
363
|
-
timestamp=run.get('startedAt')
|
|
364
|
-
))
|
|
365
|
-
task_edges.append(CollaborationEdge(
|
|
366
|
-
id=f"edge-{agent_id}-{task_id}",
|
|
367
|
-
source=agent_id,
|
|
368
|
-
target=task_id,
|
|
369
|
-
type="calls",
|
|
370
|
-
label="执行"
|
|
371
|
-
))
|
|
372
|
-
if requester_id and requester_id != agent_id:
|
|
373
|
-
task_edges.append(CollaborationEdge(
|
|
374
|
-
id=f"edge-spawn-{requester_id}-{task_id}",
|
|
375
|
-
source=requester_id,
|
|
376
|
-
target=task_id,
|
|
377
|
-
type="delegates",
|
|
378
|
-
label="派发"
|
|
379
|
-
))
|
|
380
|
-
active_path.extend([main_agent_id, agent_id, task_id])
|
|
381
|
-
except Exception as e:
|
|
382
|
-
print(f"Error building collaboration dynamic: {e}")
|
|
383
|
-
|
|
384
|
-
recent_calls_raw = _get_recent_model_calls(30)
|
|
385
|
-
model_calls = [
|
|
386
|
-
ModelCall(
|
|
387
|
-
id=f"call-{i}",
|
|
388
|
-
agentId=r["agentId"],
|
|
389
|
-
model=r.get("model", ""),
|
|
390
|
-
sessionId=r.get("sessionId", ""),
|
|
391
|
-
trigger=r.get("trigger", ""),
|
|
392
|
-
tokens=r.get("tokens", 0),
|
|
393
|
-
timestamp=r.get("timestamp", 0),
|
|
394
|
-
time=r.get("time", "")
|
|
395
|
-
)
|
|
396
|
-
for i, r in enumerate(recent_calls_raw)
|
|
397
|
-
]
|
|
398
|
-
|
|
399
|
-
return CollaborationDynamic(
|
|
400
|
-
activePath=list(set(active_path)),
|
|
401
|
-
recentCalls=model_calls,
|
|
402
|
-
agentStatuses=agent_statuses,
|
|
403
|
-
taskNodes=task_nodes,
|
|
404
|
-
taskEdges=task_edges,
|
|
405
|
-
mainAgentId=main_agent_id,
|
|
406
|
-
lastUpdate=int(__import__('time').time() * 1000),
|
|
407
|
-
)
|
package/dashboard/errors.py
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
错误中心 API - 聚合 stopReason=error 与 model-failures.log
|
|
3
|
-
"""
|
|
4
|
-
from fastapi import APIRouter
|
|
5
|
-
from typing import List, Dict, Any
|
|
6
|
-
import sys
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
|
|
9
|
-
sys.path.append(str(Path(__file__).parent.parent))
|
|
10
|
-
|
|
11
|
-
router = APIRouter()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
@router.get("/errors")
|
|
15
|
-
async def get_errors(limit: int = 50):
|
|
16
|
-
"""
|
|
17
|
-
获取错误中心数据:聚合各 Agent 的 session 错误与 model-failures.log
|
|
18
|
-
"""
|
|
19
|
-
from data.session_reader import get_recent_messages
|
|
20
|
-
from data.config_reader import get_agents_list
|
|
21
|
-
from status.error_detector import parse_failure_log
|
|
22
|
-
|
|
23
|
-
result = {"sessionErrors": [], "modelFailures": []}
|
|
24
|
-
|
|
25
|
-
# 1. Session 错误:遍历各 Agent 的 jsonl,找 stopReason=error
|
|
26
|
-
agents = get_agents_list()
|
|
27
|
-
for agent in agents:
|
|
28
|
-
agent_id = agent.get('id', '')
|
|
29
|
-
if not agent_id:
|
|
30
|
-
continue
|
|
31
|
-
messages = get_recent_messages(agent_id, limit=100)
|
|
32
|
-
for msg in messages:
|
|
33
|
-
if msg.get('stopReason') != 'error':
|
|
34
|
-
continue
|
|
35
|
-
err_type = 'unknown'
|
|
36
|
-
err_msg = msg.get('errorMessage', '') or ''
|
|
37
|
-
if '429' in err_msg or 'rate limit' in err_msg.lower():
|
|
38
|
-
err_type = 'rate-limit'
|
|
39
|
-
elif 'token' in err_msg.lower() or 'context' in err_msg.lower():
|
|
40
|
-
err_type = 'token-limit'
|
|
41
|
-
elif 'timeout' in err_msg.lower() or '超时' in err_msg:
|
|
42
|
-
err_type = 'timeout'
|
|
43
|
-
result["sessionErrors"].append({
|
|
44
|
-
"agentId": agent_id,
|
|
45
|
-
"type": err_type,
|
|
46
|
-
"message": err_msg[:200] if err_msg else "(无详情)",
|
|
47
|
-
"timestamp": msg.get('timestamp', 0),
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
result["sessionErrors"].sort(key=lambda x: x["timestamp"], reverse=True)
|
|
51
|
-
result["sessionErrors"] = result["sessionErrors"][:limit]
|
|
52
|
-
|
|
53
|
-
# 2. Model failures log
|
|
54
|
-
failures = parse_failure_log()
|
|
55
|
-
for f in failures[:limit]:
|
|
56
|
-
result["modelFailures"].append({
|
|
57
|
-
"model": f.get("model", ""),
|
|
58
|
-
"errorType": f.get("error_type", ""),
|
|
59
|
-
"message": (f.get("message", "") or "")[:200],
|
|
60
|
-
"timestamp": f.get("timestamp", 0),
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
return result
|