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,796 @@
1
+ <template>
2
+ <div class="agent-card" :class="[`status-${agent.status}`, { 'is-main': isMain }]" @click="$emit('click')">
3
+ <!-- 警告指示器(独立于卡片颜色) -->
4
+ <div v-if="error || stuckWarning" class="warning-indicator" :class="{ 'has-error': error }" @click.stop="showWarningDetail">
5
+ <span class="indicator-icon">{{ error ? '⚠️' : '⏳' }}</span>
6
+ <span class="indicator-count" v-if="stuckWarning">{{ stuckWarning.idleSeconds }}s</span>
7
+ </div>
8
+
9
+ <div class="card-header">
10
+ <div class="avatar">{{ emoji }}</div>
11
+ <div class="header-info">
12
+ <div class="name">{{ agent.name }}</div>
13
+ <div class="status-pill" :class="`status-${agent.status}`">
14
+ <span class="status-dot" :class="`status-${agent.status}`"></span>
15
+ <span class="status-text">{{ statusText }}</span>
16
+ </div>
17
+ </div>
18
+ <div v-if="isMain" class="main-badge">PM</div>
19
+ </div>
20
+
21
+ <div class="card-body">
22
+ <!-- 模型信息 -->
23
+ <div v-if="modelInfo && modelInfo.primary" class="model-row">
24
+ <span class="model-label">模型</span>
25
+ <span class="model-value">{{ shortModelId(modelInfo.primary) }}</span>
26
+ <span v-if="modelInfo.fallbacks?.length" class="model-fallbacks" :title="'备用: ' + modelInfo.fallbacks.map(shortModelId).join(', ')">
27
+ <span v-for="(fb, idx) in modelInfo.fallbacks.slice(0, 2)" :key="fb" class="fallback-tag">
28
+ {{ shortModelId(fb) }}
29
+ </span>
30
+ <span v-if="modelInfo.fallbacks.length > 2" class="fallback-more">+{{ modelInfo.fallbacks.length - 2 }}</span>
31
+ </span>
32
+ </div>
33
+
34
+ <!-- 当前任务(单任务模式) -->
35
+ <div v-if="displayTaskName && !showTaskList" class="current-task">
36
+ <div class="task-header">
37
+ <span class="task-icon">▶</span>
38
+ <span class="task-label">当前任务</span>
39
+ </div>
40
+ <div class="task-name" :title="displayTaskName">{{ displayTaskName }}</div>
41
+ <div v-if="displayTaskChildAgent" class="task-child-info">
42
+ <span class="child-arrow">→</span>
43
+ <span class="child-name">{{ displayTaskChildAgent }}</span>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- 多任务并行展示 -->
48
+ <div v-if="showTaskList" class="multi-tasks">
49
+ <div class="tasks-header" @click.stop="tasksExpanded = !tasksExpanded">
50
+ <span class="tasks-icon">📋</span>
51
+ <span class="tasks-label">并行任务</span>
52
+ <span class="tasks-count">{{ taskCount }}</span>
53
+ <span class="tasks-toggle">{{ tasksExpanded ? '▲' : '▼' }}</span>
54
+ </div>
55
+ <div class="tasks-list" :class="{ expanded: tasksExpanded }">
56
+ <div v-for="task in visibleTasks" :key="task.id" class="task-item" :class="`task-status-${task.status}`">
57
+ <span class="task-status-dot"></span>
58
+ <span class="task-name" :title="task.name">{{ task.name }}</span>
59
+ <span v-if="task.childAgentId" class="task-child-agent">→ {{ task.childAgentId }}</span>
60
+ </div>
61
+ <div v-if="hiddenTaskCount > 0" class="tasks-more">
62
+ +{{ hiddenTaskCount }} 更多任务
63
+ </div>
64
+ </div>
65
+ </div>
66
+
67
+ <!-- 详细状态(工作中时显示) -->
68
+ <div v-if="agent.status === 'working' && currentAction" class="status-detail" :class="`sub-status-${subStatus}`">
69
+ <span class="action-icon">{{ subStatusIcon }}</span>
70
+ <span class="action-text">{{ currentAction }}</span>
71
+ </div>
72
+
73
+ <!-- 空闲状态 -->
74
+ <div v-else-if="agent.status === 'idle'" class="idle-hint">
75
+ 空闲中,等待任务...
76
+ </div>
77
+ </div>
78
+
79
+ <!-- 警告详情弹窗 -->
80
+ <Teleport to="body">
81
+ <div v-if="showDetailModal" class="warning-modal-overlay" @click.self="showDetailModal = false">
82
+ <div class="warning-modal">
83
+ <div class="modal-header">
84
+ <span class="modal-icon">{{ error ? '⚠️' : '⏳' }}</span>
85
+ <span class="modal-title">{{ error ? '异常详情' : '卡顿分析' }}</span>
86
+ <button class="modal-close" @click="showDetailModal = false">×</button>
87
+ </div>
88
+ <div class="modal-body">
89
+ <!-- 错误详情 -->
90
+ <template v-if="error">
91
+ <div class="detail-row">
92
+ <span class="detail-label">类型</span>
93
+ <span class="detail-value error-type-tag">{{ errorTypeLabel }}</span>
94
+ </div>
95
+ <div class="detail-row">
96
+ <span class="detail-label">信息</span>
97
+ <span class="detail-value">{{ error.message }}</span>
98
+ </div>
99
+ </template>
100
+ <!-- 卡顿详情 -->
101
+ <template v-else-if="stuckWarning">
102
+ <div class="detail-row">
103
+ <span class="detail-label">等待时间</span>
104
+ <span class="detail-value highlight">{{ stuckWarning.idleSeconds }} 秒</span>
105
+ </div>
106
+ <div class="detail-row" v-if="stuckWarning.reason">
107
+ <span class="detail-label">原因</span>
108
+ <span class="detail-value">{{ stuckReasonLabel }}</span>
109
+ </div>
110
+ <div class="detail-row" v-if="stuckWarning.reasonDetail">
111
+ <span class="detail-label">详情</span>
112
+ <span class="detail-value">{{ stuckWarning.reasonDetail }}</span>
113
+ </div>
114
+ <div class="detail-row" v-if="stuckWarning.waitingFor">
115
+ <span class="detail-label">等待对象</span>
116
+ <span class="detail-value agent-link" @click="navigateToAgent(stuckWarning.waitingFor.agentId)">
117
+ {{ stuckWarning.waitingFor.agentId }}
118
+ <span v-if="stuckWarning.waitingFor.task" class="waiting-task">({{ stuckWarning.waitingFor.task }})</span>
119
+ </span>
120
+ </div>
121
+ <div class="stuck-suggestion" v-if="stuckSuggestion">
122
+ <span class="suggestion-icon">💡</span>
123
+ <span>{{ stuckSuggestion }}</span>
124
+ </div>
125
+ </template>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </Teleport>
130
+ </div>
131
+ </template>
132
+
133
+ <script setup lang="ts">
134
+ import { computed, ref } from 'vue'
135
+
136
+ interface Agent {
137
+ name: string
138
+ status: 'idle' | 'working' | 'down'
139
+ lastActiveFormatted?: string
140
+ }
141
+
142
+ interface AgentError {
143
+ hasError: boolean
144
+ type: string
145
+ message: string
146
+ timestamp: number
147
+ }
148
+
149
+ interface StuckWarning {
150
+ isStuck: boolean
151
+ idleSeconds: number
152
+ lastUpdate: number
153
+ reason?: 'self_busy' | 'waiting_for_child' | 'unknown'
154
+ reasonDetail?: string
155
+ waitingFor?: {
156
+ agentId: string
157
+ task?: string
158
+ }
159
+ }
160
+
161
+ /** 活跃任务(多任务并行展示) */
162
+ interface ActiveTask {
163
+ id: string
164
+ name: string
165
+ status: 'working' | 'retrying' | 'failed'
166
+ timestamp?: number
167
+ childAgentId?: string
168
+ featureId?: string
169
+ }
170
+
171
+ const props = defineProps<{
172
+ agent: Agent
173
+ modelInfo?: { primary?: string; fallbacks?: string[] }
174
+ isMain?: boolean
175
+ currentTask?: string
176
+ error?: AgentError
177
+ stuckWarning?: StuckWarning
178
+ hierarchyDepth?: number
179
+ agentColor?: string
180
+ // TR5: 详细状态
181
+ subStatus?: 'thinking' | 'tool_executing' | 'waiting_llm' | 'waiting_child'
182
+ currentAction?: string
183
+ toolName?: string
184
+ waitingFor?: string
185
+ // 多任务并行:活跃任务列表
186
+ agentTasks?: ActiveTask[]
187
+ }>()
188
+
189
+ const emit = defineEmits<{
190
+ click: []
191
+ 'navigate-agent': [agentId: string]
192
+ }>()
193
+
194
+ const showDetailModal = ref(false)
195
+ const tasksExpanded = ref(false)
196
+
197
+ const EMOJI_POOL = ['🤖', '👤', '📊', '🏗️', '💻', '🧪', '🔧', '📋', '🎯', '⚙️']
198
+
199
+ const emoji = computed(() => {
200
+ const name = (props.agent.name || '').toLowerCase()
201
+ if (name.includes('pm') || name.includes('project') || name.includes('主')) return '👨‍💼'
202
+ if (name.includes('analyst') || name.includes('分析')) return '📊'
203
+ if (name.includes('architect') || name.includes('架构')) return '🏗️'
204
+ if (name.includes('dev') || name.includes('开发')) return '💻'
205
+ if (name.includes('qa') || name.includes('test') || name.includes('测试')) return '🧪'
206
+ if (name.includes('ops') || name.includes('运维')) return '🔧'
207
+ if (name.includes('frontend') || name.includes('前端')) return '🎨'
208
+ if (name.includes('backend') || name.includes('后端')) return '⚙️'
209
+ let hash = 0
210
+ for (let i = 0; i < name.length; i++) hash = (hash << 5) - hash + name.charCodeAt(i)
211
+ return EMOJI_POOL[Math.abs(hash) % EMOJI_POOL.length]
212
+ })
213
+
214
+ const statusText = computed(() => {
215
+ const map = { idle: '空闲', working: '工作中', down: '异常' }
216
+ return map[props.agent.status] || '未知'
217
+ })
218
+
219
+ const taskCount = computed(() => props.agentTasks?.length || 0)
220
+
221
+ // 只有主 Agent 且有 2+ 任务时才显示"并行任务"列表
222
+ // 子 Agent 或单任务直接显示任务内容
223
+ const showTaskList = computed(() => props.isMain && taskCount.value >= 2)
224
+
225
+ const visibleTasks = computed(() => {
226
+ if (!props.agentTasks) return []
227
+ // 最多显示3个任务,其余折叠
228
+ return props.agentTasks.slice(0, 3)
229
+ })
230
+
231
+ const hiddenTaskCount = computed(() => {
232
+ if (!props.agentTasks) return 0
233
+ return Math.max(0, props.agentTasks.length - 3)
234
+ })
235
+
236
+ // 单任务时显示的任务名(优先使用 agentTasks,否则用 currentTask)
237
+ const displayTaskName = computed(() => {
238
+ if (props.agentTasks && props.agentTasks.length === 1) {
239
+ return props.agentTasks[0].name
240
+ }
241
+ return props.currentTask
242
+ })
243
+
244
+ // 单任务时的子 Agent(仅 agentTasks 有此信息)
245
+ const displayTaskChildAgent = computed(() => {
246
+ if (props.agentTasks && props.agentTasks.length === 1) {
247
+ return props.agentTasks[0].childAgentId
248
+ }
249
+ return undefined
250
+ })
251
+
252
+ const subStatusIcon = computed(() => {
253
+ const icons: Record<string, string> = {
254
+ 'thinking': '🧠',
255
+ 'tool_executing': '⚙️',
256
+ 'waiting_llm': '📡',
257
+ 'waiting_child': '⏳',
258
+ }
259
+ return icons[props.subStatus || ''] || '🔄'
260
+ })
261
+
262
+ const errorTypeLabel = computed(() => {
263
+ const labels: Record<string, string> = {
264
+ 'rate-limit': '请求过快',
265
+ 'token-limit': 'Token 超限',
266
+ 'timeout': '请求超时',
267
+ 'quota': '余额不足',
268
+ 'unknown': '发生错误'
269
+ }
270
+ return labels[props.error?.type || 'unknown'] || '发生错误'
271
+ })
272
+
273
+ const stuckReasonLabel = computed(() => {
274
+ const labels: Record<string, string> = {
275
+ 'waiting_for_child': '等待子代理响应',
276
+ 'self_busy': '自身处理中',
277
+ 'unknown': '原因未明'
278
+ }
279
+ return labels[props.stuckWarning?.reason || 'unknown'] || '原因未明'
280
+ })
281
+
282
+ const stuckSuggestion = computed(() => {
283
+ if (!props.stuckWarning) return null
284
+ const reason = props.stuckWarning.reason
285
+ const seconds = props.stuckWarning.idleSeconds
286
+
287
+ if (reason === 'waiting_for_child') {
288
+ if (seconds > 180) {
289
+ return '子代理响应时间过长,建议检查子代理状态或考虑终止任务'
290
+ }
291
+ return '子代理正在执行任务,请耐心等待'
292
+ }
293
+ if (reason === 'self_busy') {
294
+ if (seconds > 120) {
295
+ return '任务处理时间过长,可能遇到复杂问题或外部资源阻塞'
296
+ }
297
+ return '正在处理复杂任务,请稍候'
298
+ }
299
+ return null
300
+ })
301
+
302
+ function shortModelId(id: string): string {
303
+ const parts = id.split('/')
304
+ return parts[parts.length - 1] || id
305
+ }
306
+
307
+ function showWarningDetail() {
308
+ showDetailModal.value = true
309
+ }
310
+
311
+ function navigateToAgent(agentId: string) {
312
+ emit('navigate-agent', agentId)
313
+ showDetailModal.value = false
314
+ }
315
+ </script>
316
+
317
+ <style scoped>
318
+ .agent-card {
319
+ display: flex;
320
+ flex-direction: column;
321
+ background: white;
322
+ border-radius: 12px;
323
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
324
+ cursor: pointer;
325
+ transition: transform 0.2s, box-shadow 0.2s;
326
+ overflow: hidden;
327
+ height: 100%;
328
+ }
329
+
330
+ .agent-card:hover {
331
+ transform: translateY(-2px);
332
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
333
+ }
334
+
335
+ .agent-card.is-main {
336
+ border: 2px solid #4a9eff;
337
+ background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
338
+ }
339
+
340
+ .agent-card.is-main:hover {
341
+ box-shadow: 0 6px 20px rgba(74, 158, 255, 0.2);
342
+ }
343
+
344
+ .agent-card.has-error {
345
+ border: 2px solid #ef4444;
346
+ background: linear-gradient(180deg, #ffffff 0%, #fef2f2 100%);
347
+ }
348
+
349
+ .agent-card.is-stuck {
350
+ border: 2px solid #f59e0b;
351
+ background: linear-gradient(180deg, #ffffff 0%, #fffbeb 100%);
352
+ }
353
+
354
+ /* 主 agent 卡片内容更大 */
355
+ .agent-card.is-main .card-header {
356
+ padding: 0.85rem 1rem;
357
+ }
358
+
359
+ .agent-card.is-main .avatar {
360
+ font-size: 2.2rem;
361
+ width: 48px;
362
+ height: 48px;
363
+ }
364
+
365
+ .agent-card.is-main .name {
366
+ font-size: 1rem;
367
+ }
368
+
369
+ .agent-card.is-main .card-body {
370
+ padding: 0.85rem 1rem;
371
+ }
372
+
373
+ .agent-card.is-main .current-task {
374
+ padding: 0.6rem 0.85rem;
375
+ }
376
+
377
+ .agent-card.is-main .current-task .task-name {
378
+ font-size: 0.85rem;
379
+ white-space: normal;
380
+ display: -webkit-box;
381
+ -webkit-line-clamp: 3;
382
+ -webkit-box-orient: vertical;
383
+ }
384
+
385
+ /* 头部 */
386
+ .card-header {
387
+ display: flex;
388
+ align-items: center;
389
+ gap: 0.75rem;
390
+ padding: 0.75rem 1rem;
391
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
392
+ border-bottom: 1px solid #e5e7eb;
393
+ }
394
+
395
+ .avatar {
396
+ font-size: 2rem;
397
+ width: 44px;
398
+ height: 44px;
399
+ display: flex;
400
+ align-items: center;
401
+ justify-content: center;
402
+ background: white;
403
+ border-radius: 10px;
404
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.06);
405
+ }
406
+
407
+ .header-info {
408
+ flex: 1;
409
+ min-width: 0;
410
+ }
411
+
412
+ .name {
413
+ font-size: 0.95rem;
414
+ font-weight: 600;
415
+ color: #1e293b;
416
+ margin-bottom: 0.25rem;
417
+ white-space: nowrap;
418
+ overflow: hidden;
419
+ text-overflow: ellipsis;
420
+ }
421
+
422
+ .status-pill {
423
+ display: inline-flex;
424
+ align-items: center;
425
+ gap: 0.35rem;
426
+ padding: 2px 8px;
427
+ border-radius: 10px;
428
+ font-size: 0.7rem;
429
+ font-weight: 500;
430
+ }
431
+
432
+ .status-pill.status-idle { background: #dcfce7; color: #166534; }
433
+ .status-pill.status-working { background: #dbeafe; color: #1d4ed8; }
434
+ .status-pill.status-down { background: #fee2e2; color: #991b1b; }
435
+
436
+ .status-dot {
437
+ width: 6px;
438
+ height: 6px;
439
+ border-radius: 50%;
440
+ }
441
+
442
+ .status-dot.status-idle { background: #22c55e; }
443
+ .status-dot.status-working { background: #3b82f6; }
444
+ .status-dot.status-down { background: #ef4444; }
445
+
446
+ .main-badge {
447
+ font-size: 0.65rem;
448
+ font-weight: 700;
449
+ color: #4a9eff;
450
+ background: rgba(74, 158, 255, 0.1);
451
+ padding: 3px 8px;
452
+ border-radius: 4px;
453
+ letter-spacing: 0.5px;
454
+ }
455
+
456
+ /* 内容区 */
457
+ .card-body {
458
+ flex: 1;
459
+ padding: 0.75rem 1rem;
460
+ display: flex;
461
+ flex-direction: column;
462
+ gap: 0.6rem;
463
+ overflow: hidden;
464
+ }
465
+
466
+ /* 模型行 */
467
+ .model-row {
468
+ display: flex;
469
+ align-items: center;
470
+ flex-wrap: wrap;
471
+ gap: 0.4rem;
472
+ font-size: 0.75rem;
473
+ }
474
+
475
+ .model-label {
476
+ color: #94a3b8;
477
+ min-width: 36px;
478
+ }
479
+
480
+ .model-value {
481
+ font-family: ui-monospace, 'Cascadia Code', monospace;
482
+ font-weight: 500;
483
+ color: #475569;
484
+ }
485
+
486
+ .model-fallbacks {
487
+ display: flex;
488
+ align-items: center;
489
+ gap: 0.25rem;
490
+ margin-left: 0.25rem;
491
+ }
492
+
493
+ .fallback-tag {
494
+ font-size: 0.6rem;
495
+ color: #64748b;
496
+ background: #f1f5f9;
497
+ padding: 1px 5px;
498
+ border-radius: 3px;
499
+ font-family: ui-monospace, monospace;
500
+ }
501
+
502
+ .fallback-more {
503
+ font-size: 0.55rem;
504
+ color: #94a3b8;
505
+ }
506
+
507
+ /* 错误警告 */
508
+ .error-warning {
509
+ background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
510
+ border: 1px solid #fca5a5;
511
+ border-radius: 8px;
512
+ padding: 0.5rem 0.75rem;
513
+ }
514
+
515
+ /* 卡顿警告 */
516
+ .stuck-warning {
517
+ background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
518
+ border: 1px solid #fcd34d;
519
+ border-radius: 8px;
520
+ padding: 0.5rem 0.75rem;
521
+ }
522
+
523
+ .warning-header {
524
+ display: flex;
525
+ align-items: center;
526
+ gap: 0.35rem;
527
+ margin-bottom: 0.2rem;
528
+ }
529
+
530
+ .warning-icon {
531
+ font-size: 0.75rem;
532
+ }
533
+
534
+ .warning-label {
535
+ font-size: 0.7rem;
536
+ font-weight: 600;
537
+ color: #991b1b;
538
+ }
539
+
540
+ .stuck-warning .warning-label {
541
+ color: #92400e;
542
+ }
543
+
544
+ .warning-message {
545
+ font-size: 0.7rem;
546
+ color: #7f1d1d;
547
+ white-space: nowrap;
548
+ overflow: hidden;
549
+ text-overflow: ellipsis;
550
+ }
551
+
552
+ .stuck-warning .warning-message {
553
+ color: #78350f;
554
+ }
555
+
556
+ /* 当前任务 */
557
+ .current-task {
558
+ background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
559
+ border: 1px solid #93c5fd;
560
+ border-radius: 8px;
561
+ padding: 0.5rem 0.75rem;
562
+ }
563
+
564
+ .task-header {
565
+ display: flex;
566
+ align-items: center;
567
+ gap: 0.35rem;
568
+ margin-bottom: 0.25rem;
569
+ }
570
+
571
+ .task-icon {
572
+ font-size: 0.65rem;
573
+ color: #3b82f6;
574
+ }
575
+
576
+ .task-label {
577
+ font-size: 0.65rem;
578
+ color: #3b82f6;
579
+ font-weight: 500;
580
+ }
581
+
582
+ .current-task .task-name {
583
+ font-size: 0.8rem;
584
+ color: #1e40af;
585
+ font-weight: 500;
586
+ white-space: nowrap;
587
+ overflow: hidden;
588
+ text-overflow: ellipsis;
589
+ }
590
+
591
+ /* 任务子 Agent 信息 */
592
+ .task-child-info {
593
+ display: flex;
594
+ align-items: center;
595
+ gap: 0.3rem;
596
+ margin-top: 0.35rem;
597
+ padding-top: 0.35rem;
598
+ border-top: 1px dashed #bfdbfe;
599
+ }
600
+
601
+ .child-arrow {
602
+ font-size: 0.7rem;
603
+ color: #64748b;
604
+ }
605
+
606
+ .child-name {
607
+ font-size: 0.7rem;
608
+ color: #64748b;
609
+ background: #f1f5f9;
610
+ padding: 1px 6px;
611
+ border-radius: 3px;
612
+ }
613
+
614
+ /* 空闲提示 */
615
+ .idle-hint {
616
+ font-size: 0.75rem;
617
+ color: #94a3b8;
618
+ text-align: center;
619
+ padding: 0.5rem;
620
+ font-style: italic;
621
+ }
622
+
623
+ /* 详细状态 (TR5) */
624
+ .status-detail {
625
+ display: flex;
626
+ align-items: center;
627
+ gap: 0.4rem;
628
+ padding: 0.4rem 0.6rem;
629
+ border-radius: 6px;
630
+ font-size: 0.75rem;
631
+ animation: pulse-subtle 2s ease-in-out infinite;
632
+ }
633
+
634
+ .status-detail.sub-status-thinking {
635
+ background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%);
636
+ border: 1px solid #fcd34d;
637
+ color: #92400e;
638
+ }
639
+
640
+ .status-detail.sub-status-tool_executing {
641
+ background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
642
+ border: 1px solid #93c5fd;
643
+ color: #1e40af;
644
+ }
645
+
646
+ .status-detail.sub-status-waiting_llm {
647
+ background: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%);
648
+ border: 1px solid #a5b4fc;
649
+ color: #3730a3;
650
+ }
651
+
652
+ .status-detail.sub-status-waiting_child {
653
+ background: linear-gradient(135deg, #fce7f3 0%, #fbcfe8 100%);
654
+ border: 1px solid #f9a8d4;
655
+ color: #9d174d;
656
+ }
657
+
658
+ .action-icon {
659
+ font-size: 0.85rem;
660
+ }
661
+
662
+ .action-text {
663
+ font-weight: 500;
664
+ white-space: nowrap;
665
+ overflow: hidden;
666
+ text-overflow: ellipsis;
667
+ }
668
+
669
+ @keyframes pulse-subtle {
670
+ 0%, 100% { opacity: 1; }
671
+ 50% { opacity: 0.7; }
672
+ }
673
+
674
+ /* 多任务并行展示 */
675
+ .multi-tasks {
676
+ background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
677
+ border: 1px solid #93c5fd;
678
+ border-radius: 8px;
679
+ overflow: hidden;
680
+ }
681
+
682
+ .tasks-header {
683
+ display: flex;
684
+ align-items: center;
685
+ gap: 0.35rem;
686
+ padding: 0.5rem 0.75rem;
687
+ cursor: pointer;
688
+ user-select: none;
689
+ }
690
+
691
+ .tasks-header:hover {
692
+ background: rgba(59, 130, 246, 0.1);
693
+ }
694
+
695
+ .tasks-icon {
696
+ font-size: 0.75rem;
697
+ }
698
+
699
+ .tasks-label {
700
+ font-size: 0.7rem;
701
+ color: #3b82f6;
702
+ font-weight: 500;
703
+ }
704
+
705
+ .tasks-count {
706
+ font-size: 0.65rem;
707
+ background: #3b82f6;
708
+ color: white;
709
+ padding: 1px 6px;
710
+ border-radius: 10px;
711
+ font-weight: 600;
712
+ margin-left: 0.25rem;
713
+ }
714
+
715
+ .tasks-toggle {
716
+ font-size: 0.6rem;
717
+ color: #64748b;
718
+ margin-left: auto;
719
+ }
720
+
721
+ .tasks-list {
722
+ max-height: 0;
723
+ overflow: hidden;
724
+ transition: max-height 0.3s ease;
725
+ }
726
+
727
+ .tasks-list.expanded {
728
+ max-height: 200px;
729
+ overflow-y: auto;
730
+ }
731
+
732
+ .task-item {
733
+ display: flex;
734
+ align-items: center;
735
+ gap: 0.4rem;
736
+ padding: 0.4rem 0.75rem;
737
+ border-top: 1px solid #e0e7ff;
738
+ font-size: 0.75rem;
739
+ }
740
+
741
+ .task-item:hover {
742
+ background: rgba(59, 130, 246, 0.05);
743
+ }
744
+
745
+ .task-status-dot {
746
+ width: 6px;
747
+ height: 6px;
748
+ border-radius: 50%;
749
+ flex-shrink: 0;
750
+ }
751
+
752
+ .task-item.task-status-working .task-status-dot {
753
+ background: #3b82f6;
754
+ animation: pulse-dot 1.5s ease-in-out infinite;
755
+ }
756
+
757
+ .task-item.task-status-retrying .task-status-dot {
758
+ background: #f59e0b;
759
+ }
760
+
761
+ .task-item.task-status-failed .task-status-dot {
762
+ background: #ef4444;
763
+ }
764
+
765
+ @keyframes pulse-dot {
766
+ 0%, 100% { opacity: 1; transform: scale(1); }
767
+ 50% { opacity: 0.6; transform: scale(0.8); }
768
+ }
769
+
770
+ .task-item .task-name {
771
+ flex: 1;
772
+ color: #1e40af;
773
+ font-weight: 500;
774
+ white-space: nowrap;
775
+ overflow: hidden;
776
+ text-overflow: ellipsis;
777
+ }
778
+
779
+ .task-child-agent {
780
+ font-size: 0.65rem;
781
+ color: #64748b;
782
+ background: #f1f5f9;
783
+ padding: 1px 5px;
784
+ border-radius: 3px;
785
+ flex-shrink: 0;
786
+ }
787
+
788
+ .tasks-more {
789
+ padding: 0.4rem 0.75rem;
790
+ font-size: 0.7rem;
791
+ color: #64748b;
792
+ text-align: center;
793
+ border-top: 1px solid #e0e7ff;
794
+ background: rgba(59, 130, 246, 0.03);
795
+ }
796
+ </style>