plugin-agent-orchestrator 1.0.21 → 1.0.23

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 (180) hide show
  1. package/client-v2.d.ts +2 -0
  2. package/client-v2.js +1 -0
  3. package/dist/client/index.js +1 -1
  4. package/dist/client-v2/214.723affb37c13bf7a.js +10 -0
  5. package/dist/client-v2/264.0533912e6c5ea2d7.js +10 -0
  6. package/dist/client-v2/41.1805b2edfaa4afe2.js +10 -0
  7. package/dist/client-v2/418.5ae055abf141820e.js +10 -0
  8. package/dist/client-v2/619.d99d3c9e61c99064.js +10 -0
  9. package/dist/client-v2/70.a15d7fcec7c41768.js +10 -0
  10. package/dist/client-v2/892.72db4161511c8a16.js +10 -0
  11. package/dist/client-v2/926.87f660b670d85bcc.js +10 -0
  12. package/dist/client-v2/index.js +10 -0
  13. package/dist/externalVersion.js +7 -6
  14. package/dist/locale/en-US.json +7 -0
  15. package/dist/locale/vi-VN.json +7 -0
  16. package/dist/locale/zh-CN.json +27 -0
  17. package/dist/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.js +63 -0
  18. package/dist/server/plugin.js +41 -1
  19. package/dist/server/services/AgentHarness.js +52 -27
  20. package/dist/server/services/AgentLoopController.js +8 -2
  21. package/dist/server/services/AgentLoopService.js +1 -1
  22. package/dist/server/services/AgentRegistryService.js +53 -42
  23. package/dist/server/services/CircuitBreaker.js +7 -2
  24. package/dist/server/services/CodeValidator.js +48 -14
  25. package/dist/server/services/SandboxRunner.js +18 -14
  26. package/dist/server/skill-hub/plugin.js +44 -17
  27. package/dist/server/tools/delegate-task.js +7 -2
  28. package/dist/server/tools/skill-execute.js +33 -2
  29. package/dist/server/utils/ai-manager.js +51 -0
  30. package/dist/server/utils/ctx-utils.js +11 -0
  31. package/dist/server/utils/skill-settings.js +122 -0
  32. package/package.json +49 -45
  33. package/src/client/AIEmployeesContext.tsx +51 -14
  34. package/src/client/AgentRunsTab.tsx +767 -764
  35. package/src/client/HarnessProfilesTab.tsx +254 -247
  36. package/src/client/RulesTab.tsx +780 -716
  37. package/src/client/TracingTab.tsx +1 -0
  38. package/src/client/plugin.tsx +34 -27
  39. package/src/client/skill-hub/components/GitSkillImport.tsx +10 -3
  40. package/src/client/skill-hub/components/SkillMetrics.tsx +157 -124
  41. package/src/client/skill-hub/index.tsx +58 -51
  42. package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +132 -99
  43. package/src/client/skill-hub/tools/registerSkillLoopCards.ts +71 -58
  44. package/src/client/tools/registerOrchestratorCards.ts +17 -7
  45. package/src/client-v2/components/AIEmployeeSelect.tsx +47 -0
  46. package/src/client-v2/components/AIEmployeesContext.tsx +110 -0
  47. package/src/client-v2/components/AgentRunsTab.tsx +767 -0
  48. package/src/client-v2/components/HarnessProfilesTab.tsx +254 -0
  49. package/src/client-v2/components/RulesTab.tsx +782 -0
  50. package/src/client-v2/components/TracingTab.tsx +432 -0
  51. package/src/client-v2/hooks/useApiRequest.ts +114 -0
  52. package/src/client-v2/pages/AgentRunsPage.tsx +13 -0
  53. package/src/client-v2/pages/ExecutionHistoryPage.tsx +10 -0
  54. package/src/client-v2/pages/HarnessProfilesPage.tsx +10 -0
  55. package/src/client-v2/pages/LoopSettingsPage.tsx +10 -0
  56. package/src/client-v2/pages/RulesPage.tsx +13 -0
  57. package/src/client-v2/pages/SkillDefinitionsPage.tsx +10 -0
  58. package/src/client-v2/pages/SkillMetricsPage.tsx +10 -0
  59. package/src/client-v2/pages/TracingPage.tsx +13 -0
  60. package/src/client-v2/plugin.tsx +70 -0
  61. package/src/client-v2/skill-hub/components/ExecutionHistory.tsx +196 -0
  62. package/src/client-v2/skill-hub/components/FileLinkList.tsx +37 -0
  63. package/src/client-v2/skill-hub/components/GitSkillImport.tsx +539 -0
  64. package/src/client-v2/skill-hub/components/LoopSettings.tsx +331 -0
  65. package/src/client-v2/skill-hub/components/SkillEditor.tsx +453 -0
  66. package/src/client-v2/skill-hub/components/SkillManager.tsx +174 -0
  67. package/src/client-v2/skill-hub/components/SkillMetrics.tsx +157 -0
  68. package/src/client-v2/skill-hub/components/SkillTestPanel.tsx +135 -0
  69. package/src/client-v2/skill-hub/locale.ts +13 -0
  70. package/src/client-v2/skill-hub/tools/loopTemplates.ts +52 -0
  71. package/src/client-v2/skill-hub/utils/jsonFields.ts +41 -0
  72. package/src/client-v2/utils/jsonFields.ts +41 -0
  73. package/src/locale/en-US.json +7 -0
  74. package/src/locale/vi-VN.json +7 -0
  75. package/src/locale/zh-CN.json +27 -0
  76. package/src/server/__tests__/agent-registry-service.test.ts +147 -0
  77. package/src/server/__tests__/code-validator.test.ts +63 -0
  78. package/src/server/__tests__/skill-execute.test.ts +33 -0
  79. package/src/server/__tests__/skill-settings.test.ts +63 -0
  80. package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +39 -0
  81. package/src/server/plugin.ts +68 -12
  82. package/src/server/services/AgentHarness.ts +49 -22
  83. package/src/server/services/AgentLoopController.ts +17 -6
  84. package/src/server/services/AgentLoopService.ts +1 -1
  85. package/src/server/services/AgentPlannerService.ts +10 -0
  86. package/src/server/services/AgentRegistryService.ts +89 -47
  87. package/src/server/services/CircuitBreaker.ts +10 -0
  88. package/src/server/services/CodeValidator.ts +237 -159
  89. package/src/server/services/SandboxRunner.ts +203 -189
  90. package/src/server/skill-hub/plugin.ts +933 -898
  91. package/src/server/tools/delegate-task.ts +12 -9
  92. package/src/server/tools/skill-execute.ts +194 -160
  93. package/src/server/utils/ai-manager.ts +24 -0
  94. package/src/server/utils/ctx-utils.ts +14 -0
  95. package/src/server/utils/skill-settings.ts +116 -0
  96. package/dist/client/AIEmployeeSelect.d.ts +0 -11
  97. package/dist/client/AIEmployeesContext.d.ts +0 -30
  98. package/dist/client/AgentRunsTab.d.ts +0 -2
  99. package/dist/client/HarnessProfilesTab.d.ts +0 -2
  100. package/dist/client/OrchestratorSettings.d.ts +0 -3
  101. package/dist/client/RulesTab.d.ts +0 -2
  102. package/dist/client/TracingTab.d.ts +0 -2
  103. package/dist/client/hooks/useRunEventStream.d.ts +0 -22
  104. package/dist/client/index.d.ts +0 -2
  105. package/dist/client/plugin.d.ts +0 -6
  106. package/dist/client/skill-hub/components/ExecutionHistory.d.ts +0 -2
  107. package/dist/client/skill-hub/components/ExecutionProgress.d.ts +0 -20
  108. package/dist/client/skill-hub/components/GitSkillImport.d.ts +0 -7
  109. package/dist/client/skill-hub/components/LoopSettings.d.ts +0 -2
  110. package/dist/client/skill-hub/components/SkillEditor.d.ts +0 -7
  111. package/dist/client/skill-hub/components/SkillManager.d.ts +0 -2
  112. package/dist/client/skill-hub/components/SkillMetrics.d.ts +0 -2
  113. package/dist/client/skill-hub/components/SkillTestPanel.d.ts +0 -7
  114. package/dist/client/skill-hub/index.d.ts +0 -11
  115. package/dist/client/skill-hub/locale.d.ts +0 -3
  116. package/dist/client/skill-hub/tools/InteractionSchemasProvider.d.ts +0 -6
  117. package/dist/client/skill-hub/tools/SkillHubCard.d.ts +0 -3
  118. package/dist/client/skill-hub/tools/loopTemplates.d.ts +0 -22
  119. package/dist/client/skill-hub/tools/registerSkillLoopCards.d.ts +0 -1
  120. package/dist/client/skill-hub/utils/jsonFields.d.ts +0 -3
  121. package/dist/client/tools/PlanApprovalCard.d.ts +0 -3
  122. package/dist/client/tools/registerOrchestratorCards.d.ts +0 -1
  123. package/dist/index.d.ts +0 -2
  124. package/dist/server/collections/agent-execution-spans.d.ts +0 -9
  125. package/dist/server/collections/agent-harness-profiles.d.ts +0 -2
  126. package/dist/server/collections/agent-loop-events.d.ts +0 -2
  127. package/dist/server/collections/agent-loop-runs.d.ts +0 -2
  128. package/dist/server/collections/agent-loop-steps.d.ts +0 -2
  129. package/dist/server/collections/orchestrator-config.d.ts +0 -2
  130. package/dist/server/collections/orchestrator-logs.d.ts +0 -8
  131. package/dist/server/collections/skill-definitions.d.ts +0 -3
  132. package/dist/server/collections/skill-executions.d.ts +0 -3
  133. package/dist/server/collections/skill-loop-configs.d.ts +0 -3
  134. package/dist/server/collections/skill-worker-configs.d.ts +0 -3
  135. package/dist/server/migrations/20260423000000-add-progress-fields.d.ts +0 -4
  136. package/dist/server/migrations/20260425000000-add-interaction-schema.d.ts +0 -4
  137. package/dist/server/migrations/20260427000000-add-tracing-detail-fields.d.ts +0 -7
  138. package/dist/server/migrations/20260427000000-change-packages-to-text.d.ts +0 -4
  139. package/dist/server/migrations/20260427000001-change-other-json-to-text.d.ts +0 -4
  140. package/dist/server/migrations/20260429000000-add-llm-fields.d.ts +0 -7
  141. package/dist/server/migrations/20260429000000-fix-inputargs-json-to-text.d.ts +0 -16
  142. package/dist/server/migrations/20260503000000-add-orchestrator-trace-fields.d.ts +0 -7
  143. package/dist/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.d.ts +0 -7
  144. package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.d.ts +0 -12
  145. package/dist/server/migrations/20260601000000-add-token-fields.d.ts +0 -7
  146. package/dist/server/plugin.d.ts +0 -16
  147. package/dist/server/resources/agent-loop.d.ts +0 -3
  148. package/dist/server/resources/tracing.d.ts +0 -7
  149. package/dist/server/services/AgentHarness.d.ts +0 -44
  150. package/dist/server/services/AgentLoopController.d.ts +0 -218
  151. package/dist/server/services/AgentLoopRepository.d.ts +0 -20
  152. package/dist/server/services/AgentLoopService.d.ts +0 -159
  153. package/dist/server/services/AgentPlanValidator.d.ts +0 -4
  154. package/dist/server/services/AgentPlannerService.d.ts +0 -8
  155. package/dist/server/services/AgentRegistryService.d.ts +0 -21
  156. package/dist/server/services/CircuitBreaker.d.ts +0 -40
  157. package/dist/server/services/CodeValidator.d.ts +0 -32
  158. package/dist/server/services/ContextAggregator.d.ts +0 -45
  159. package/dist/server/services/ExecutionSpanService.d.ts +0 -46
  160. package/dist/server/services/FileManager.d.ts +0 -28
  161. package/dist/server/services/RunEventBus.d.ts +0 -9
  162. package/dist/server/services/SandboxRunner.d.ts +0 -41
  163. package/dist/server/services/SkillManager.d.ts +0 -6
  164. package/dist/server/services/SkillRepositoryService.d.ts +0 -22
  165. package/dist/server/services/TokenTracker.d.ts +0 -62
  166. package/dist/server/services/WorkerEnvManager.d.ts +0 -26
  167. package/dist/server/skill-hub/actions/git-import.d.ts +0 -21
  168. package/dist/server/skill-hub/mcp/McpController.d.ts +0 -15
  169. package/dist/server/skill-hub/plugin.d.ts +0 -61
  170. package/dist/server/skill-hub/tasks/SkillExecutionTask.d.ts +0 -16
  171. package/dist/server/skill-hub/utils/json-fields.d.ts +0 -7
  172. package/dist/server/tools/agent-loop.d.ts +0 -235
  173. package/dist/server/tools/delegate-task.d.ts +0 -19
  174. package/dist/server/tools/external-rag-search.d.ts +0 -42
  175. package/dist/server/tools/orchestrator-plan.d.ts +0 -205
  176. package/dist/server/tools/skill-execute.d.ts +0 -36
  177. package/dist/server/types.d.ts +0 -47
  178. package/dist/server/utils/ctx-utils.d.ts +0 -30
  179. package/dist/server/utils/logging.d.ts +0 -6
  180. /package/{dist/server/index.d.ts → src/client-v2/index.tsx} +0 -0
@@ -0,0 +1,767 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import {
3
+ Alert,
4
+ Button,
5
+ Card,
6
+ Collapse,
7
+ Descriptions,
8
+ Drawer,
9
+ Empty,
10
+ Form,
11
+ message,
12
+ Popconfirm,
13
+ Select,
14
+ Space,
15
+ Spin,
16
+ Table,
17
+ Tag,
18
+ Timeline,
19
+ Typography,
20
+ } from 'antd';
21
+ import {
22
+ BranchesOutlined,
23
+ CheckCircleOutlined,
24
+ ClockCircleOutlined,
25
+ CloseCircleOutlined,
26
+ EyeOutlined,
27
+ PauseCircleOutlined,
28
+ PlayCircleOutlined,
29
+ RedoOutlined,
30
+ ReloadOutlined,
31
+ StopOutlined,
32
+ } from '@ant-design/icons';
33
+ import { useApiClient as useAPIClient, useRequest } from '../hooks/useApiRequest';
34
+ import { useAIEmployees } from './AIEmployeesContext';
35
+ import { parseJsonText } from '../utils/jsonFields';
36
+
37
+ const { Paragraph, Text } = Typography;
38
+
39
+ type FilterState = {
40
+ leader?: string;
41
+ status?: string;
42
+ };
43
+
44
+ const terminalRunStatuses = new Set(['succeeded', 'failed', 'rejected', 'canceled']);
45
+
46
+ function statusColor(status?: string) {
47
+ switch (status) {
48
+ case 'succeeded':
49
+ case 'success':
50
+ return 'success';
51
+ case 'failed':
52
+ case 'error':
53
+ return 'error';
54
+ case 'waiting_user':
55
+ case 'waiting_plan_approval':
56
+ case 'needs_replan':
57
+ return 'warning';
58
+ case 'approved':
59
+ case 'running':
60
+ case 'planning':
61
+ return 'processing';
62
+ case 'rejected':
63
+ case 'canceled':
64
+ case 'skipped':
65
+ return 'default';
66
+ default:
67
+ return 'default';
68
+ }
69
+ }
70
+
71
+ function statusIcon(status?: string) {
72
+ switch (status) {
73
+ case 'succeeded':
74
+ case 'success':
75
+ return <CheckCircleOutlined />;
76
+ case 'failed':
77
+ case 'error':
78
+ return <CloseCircleOutlined />;
79
+ case 'waiting_user':
80
+ case 'waiting_plan_approval':
81
+ case 'needs_replan':
82
+ return <PauseCircleOutlined />;
83
+ case 'approved':
84
+ case 'running':
85
+ case 'planning':
86
+ return <ClockCircleOutlined />;
87
+ default:
88
+ return undefined;
89
+ }
90
+ }
91
+
92
+ function timelineColor(status?: string) {
93
+ switch (status) {
94
+ case 'succeeded':
95
+ case 'success':
96
+ return 'green';
97
+ case 'failed':
98
+ case 'error':
99
+ return 'red';
100
+ case 'waiting_user':
101
+ case 'waiting_plan_approval':
102
+ case 'needs_replan':
103
+ return 'orange';
104
+ case 'approved':
105
+ case 'running':
106
+ case 'planning':
107
+ return 'blue';
108
+ default:
109
+ return 'gray';
110
+ }
111
+ }
112
+
113
+ function StatusTag({ status }: { status?: string }) {
114
+ return (
115
+ <Tag icon={statusIcon(status)} color={statusColor(status)}>
116
+ {status || '-'}
117
+ </Tag>
118
+ );
119
+ }
120
+
121
+ function formatDate(value?: string) {
122
+ return value ? new Date(value).toLocaleString() : '-';
123
+ }
124
+
125
+ function formatDuration(start?: string, end?: string) {
126
+ if (!start) return '-';
127
+ const startMs = new Date(start).getTime();
128
+ const endMs = end ? new Date(end).getTime() : Date.now();
129
+ const diff = Math.max(0, endMs - startMs);
130
+ if (diff >= 60000) return `${Math.round(diff / 60000)}m`;
131
+ if (diff >= 1000) return `${(diff / 1000).toFixed(1)}s`;
132
+ return `${diff}ms`;
133
+ }
134
+
135
+ function formatJson(value: any) {
136
+ if (value === undefined || value === null || value === '') return '';
137
+ if (typeof value === 'string') return value;
138
+ try {
139
+ return JSON.stringify(value, null, 2);
140
+ } catch {
141
+ return String(value);
142
+ }
143
+ }
144
+
145
+ function buildSkillFileUrl(execution: any, file: any) {
146
+ if (file?.downloadUrl) return file.downloadUrl;
147
+ if (!execution?.id || !file?.name) return '';
148
+ return `/api/skillHub:download?execId=${execution.id}&filename=${encodeURIComponent(file.name)}`;
149
+ }
150
+
151
+ function renderSkillFileLink(execution: any, file: any, index: number) {
152
+ const url = buildSkillFileUrl(execution, file);
153
+ const label = file.name || file.path || `file-${index + 1}`;
154
+ return url ? (
155
+ <a key={index} href={url} target="_blank" rel="noreferrer">
156
+ {label}
157
+ </a>
158
+ ) : (
159
+ <Text key={index}>{label}</Text>
160
+ );
161
+ }
162
+
163
+ function TextBlock({ value, rows = 10 }: { value: any; rows?: number }) {
164
+ const text = formatJson(value);
165
+ if (!text) return <Text type="secondary">-</Text>;
166
+ return (
167
+ <Paragraph
168
+ style={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word', margin: 0, fontSize: 12 }}
169
+ ellipsis={{ rows, expandable: true }}
170
+ >
171
+ {text}
172
+ </Paragraph>
173
+ );
174
+ }
175
+
176
+ export const AgentRunsTab: React.FC = () => {
177
+ const api = useAPIClient();
178
+ const { employees, employeeMap } = useAIEmployees();
179
+ const [filters, setFilters] = useState<FilterState>({});
180
+ const [page, setPage] = useState(1);
181
+ const [pageSize, setPageSize] = useState(20);
182
+ const [selectedRun, setSelectedRun] = useState<any>(null);
183
+ const [detail, setDetail] = useState<any>(null);
184
+ const [detailLoading, setDetailLoading] = useState(false);
185
+ const [actionLoading, setActionLoading] = useState(false);
186
+
187
+ const requestParams = useMemo(() => {
188
+ const filter: any = {};
189
+ if (filters.leader) filter.leaderUsername = filters.leader;
190
+ if (filters.status) filter.status = filters.status;
191
+ return {
192
+ sort: ['-createdAt'],
193
+ page,
194
+ pageSize,
195
+ filter,
196
+ };
197
+ }, [filters, page, pageSize]);
198
+
199
+ const { data, loading, refresh } = useRequest(
200
+ {
201
+ url: 'agentLoops:list',
202
+ params: requestParams,
203
+ },
204
+ {
205
+ refreshDeps: [requestParams],
206
+ },
207
+ );
208
+
209
+ const runs = useMemo(() => {
210
+ const rows = (data as any)?.data;
211
+ return Array.isArray(rows) ? rows : [];
212
+ }, [data]);
213
+
214
+ const total = useMemo(() => {
215
+ const count = (data as any)?.meta?.count;
216
+ return typeof count === 'number' ? count : 0;
217
+ }, [data]);
218
+
219
+ const employeeOptions = useMemo(
220
+ () =>
221
+ employees.map((employee) => ({
222
+ label: employee.nickname || employee.username,
223
+ value: employee.username,
224
+ })),
225
+ [employees],
226
+ );
227
+
228
+ const fetchDetail = async (runId: string | number, seed?: any) => {
229
+ setSelectedRun(seed || selectedRun || { id: runId });
230
+ setDetailLoading(true);
231
+ try {
232
+ const res = await api.request({
233
+ url: 'agentLoops:get',
234
+ params: { filterByTk: runId },
235
+ });
236
+ setDetail((res as any)?.data?.data || (res as any)?.data || null);
237
+ } finally {
238
+ setDetailLoading(false);
239
+ }
240
+ };
241
+
242
+ const updateFilter = (patch: Partial<FilterState>) => {
243
+ setFilters((prev) => ({ ...prev, ...patch }));
244
+ setPage(1);
245
+ };
246
+
247
+ const resetFilters = () => {
248
+ setFilters({});
249
+ setPage(1);
250
+ };
251
+
252
+ const runAction = async (action: 'cancel' | 'resume', runId: string | number) => {
253
+ setActionLoading(true);
254
+ try {
255
+ await api.request({
256
+ url: action === 'cancel' ? 'agentLoops:cancel' : 'agentLoops:resume',
257
+ method: 'POST',
258
+ data: action === 'cancel' ? { runId } : { runId, stepId: detail?.run?.currentStepId, approved: true },
259
+ });
260
+ message.success(action === 'cancel' ? 'Run canceled' : 'Run resumed');
261
+ refresh();
262
+ await fetchDetail(runId);
263
+ } catch (error: any) {
264
+ message.error(error?.message || `Failed to ${action} run`);
265
+ } finally {
266
+ setActionLoading(false);
267
+ }
268
+ };
269
+
270
+ const retryStep = async (stepId: string | number) => {
271
+ const runId = detail?.run?.id || selectedRun?.id;
272
+ setActionLoading(true);
273
+ try {
274
+ await api.request({
275
+ url: 'agentLoops:retryStep',
276
+ method: 'POST',
277
+ data: { stepId },
278
+ });
279
+ message.success('Step queued for retry');
280
+ refresh();
281
+ if (runId) await fetchDetail(runId);
282
+ } catch (error: any) {
283
+ message.error(error?.message || 'Failed to retry step');
284
+ } finally {
285
+ setActionLoading(false);
286
+ }
287
+ };
288
+
289
+ const columns = [
290
+ {
291
+ title: 'Time',
292
+ dataIndex: 'createdAt',
293
+ key: 'createdAt',
294
+ width: 170,
295
+ render: formatDate,
296
+ },
297
+ {
298
+ title: 'Leader',
299
+ dataIndex: 'leaderUsername',
300
+ key: 'leaderUsername',
301
+ width: 160,
302
+ render: (username: string) => <Tag color="blue">{employeeMap.get(username) || username || '-'}</Tag>,
303
+ },
304
+ {
305
+ title: 'Goal',
306
+ dataIndex: 'goal',
307
+ key: 'goal',
308
+ render: (goal: string) => (
309
+ <Text ellipsis style={{ maxWidth: 380 }}>
310
+ {goal || '-'}
311
+ </Text>
312
+ ),
313
+ },
314
+ {
315
+ title: 'Status',
316
+ dataIndex: 'status',
317
+ key: 'status',
318
+ width: 130,
319
+ render: (status: string) => <StatusTag status={status} />,
320
+ },
321
+ {
322
+ title: 'Iterations',
323
+ dataIndex: 'iterationCount',
324
+ key: 'iterationCount',
325
+ width: 90,
326
+ render: (value: number) => value ?? 0,
327
+ },
328
+ {
329
+ title: 'Duration',
330
+ key: 'duration',
331
+ width: 100,
332
+ render: (_: any, record: any) => formatDuration(record.startedAt || record.createdAt, record.endedAt),
333
+ },
334
+ {
335
+ title: '',
336
+ key: 'actions',
337
+ width: 90,
338
+ render: (_: any, record: any) => (
339
+ <Button type="link" size="small" icon={<EyeOutlined />} onClick={() => fetchDetail(record.id, record)}>
340
+ Detail
341
+ </Button>
342
+ ),
343
+ },
344
+ ];
345
+
346
+ const steps = Array.isArray(detail?.steps) ? detail.steps : [];
347
+ const events = Array.isArray(detail?.events) ? detail.events : [];
348
+ const spans = Array.isArray(detail?.spans) ? detail.spans : [];
349
+ const skillExecutions = Array.isArray(detail?.skillExecutions) ? detail.skillExecutions : [];
350
+ const run = detail?.run || selectedRun;
351
+ const hasFilters = Boolean(filters.leader || filters.status);
352
+
353
+ const stepColumns = [
354
+ {
355
+ title: '#',
356
+ dataIndex: 'index',
357
+ key: 'index',
358
+ width: 56,
359
+ render: (value: number) => Number(value ?? 0) + 1,
360
+ },
361
+ {
362
+ title: 'Status',
363
+ dataIndex: 'status',
364
+ key: 'status',
365
+ width: 130,
366
+ render: (status: string) => <StatusTag status={status} />,
367
+ },
368
+ {
369
+ title: 'Type',
370
+ dataIndex: 'type',
371
+ key: 'type',
372
+ width: 110,
373
+ render: (type: string) => <Tag>{type}</Tag>,
374
+ },
375
+ {
376
+ title: 'Step',
377
+ key: 'step',
378
+ render: (_: any, record: any) => (
379
+ <Space direction="vertical" size={2} style={{ width: '100%' }}>
380
+ <Text strong>{record.title || record.planKey}</Text>
381
+ {record.description && <Text type="secondary">{record.description}</Text>}
382
+ {record.target && <Text code>{record.target}</Text>}
383
+ </Space>
384
+ ),
385
+ },
386
+ {
387
+ title: 'Depends',
388
+ dataIndex: 'dependsOn',
389
+ key: 'dependsOn',
390
+ width: 130,
391
+ render: (dependsOn: string[]) =>
392
+ Array.isArray(dependsOn) && dependsOn.length ? dependsOn.map((key) => <Tag key={key}>{key}</Tag>) : '-',
393
+ },
394
+ {
395
+ title: 'Attempts',
396
+ key: 'attempts',
397
+ width: 90,
398
+ render: (_: any, record: any) => `${record.attempt || 0}/${record.maxAttempts || 0}`,
399
+ },
400
+ {
401
+ title: '',
402
+ key: 'actions',
403
+ width: 90,
404
+ render: (_: any, record: any) =>
405
+ record.status === 'failed' && Number(record.attempt || 0) < Number(record.maxAttempts || 0) ? (
406
+ <Button
407
+ type="link"
408
+ size="small"
409
+ icon={<RedoOutlined />}
410
+ loading={actionLoading}
411
+ onClick={() => retryStep(record.id)}
412
+ >
413
+ Retry
414
+ </Button>
415
+ ) : null,
416
+ },
417
+ ];
418
+
419
+ return (
420
+ <div>
421
+ <Alert
422
+ type="info"
423
+ showIcon
424
+ style={{ marginBottom: 16 }}
425
+ message="Agent Runs"
426
+ description={
427
+ <Text type="secondary">
428
+ Persistent loop runs created by the orchestrator tools. Each run stores the goal, plan, step state,
429
+ approvals, and linked Skill Hub or sub-agent traces.
430
+ </Text>
431
+ }
432
+ />
433
+
434
+ <Card bordered={false}>
435
+ <Form layout="inline" style={{ marginBottom: 16, rowGap: 8, flexWrap: 'wrap' }}>
436
+ <Form.Item label="Leader">
437
+ <Select
438
+ allowClear
439
+ showSearch
440
+ optionFilterProp="label"
441
+ placeholder="Any leader"
442
+ style={{ minWidth: 180 }}
443
+ options={employeeOptions}
444
+ value={filters.leader}
445
+ onChange={(value) => updateFilter({ leader: value })}
446
+ />
447
+ </Form.Item>
448
+ <Form.Item label="Status">
449
+ <Select
450
+ allowClear
451
+ placeholder="Any status"
452
+ style={{ minWidth: 160 }}
453
+ value={filters.status}
454
+ onChange={(value) => updateFilter({ status: value })}
455
+ options={[
456
+ { label: 'Planning', value: 'planning' },
457
+ { label: 'Waiting plan approval', value: 'waiting_plan_approval' },
458
+ { label: 'Approved', value: 'approved' },
459
+ { label: 'Running', value: 'running' },
460
+ { label: 'Waiting user', value: 'waiting_user' },
461
+ { label: 'Needs replan', value: 'needs_replan' },
462
+ { label: 'Succeeded', value: 'succeeded' },
463
+ { label: 'Failed', value: 'failed' },
464
+ { label: 'Rejected', value: 'rejected' },
465
+ { label: 'Canceled', value: 'canceled' },
466
+ ]}
467
+ />
468
+ </Form.Item>
469
+ <Form.Item>
470
+ <Space>
471
+ <Button onClick={resetFilters} disabled={!hasFilters}>
472
+ Reset
473
+ </Button>
474
+ <Button icon={<ReloadOutlined />} onClick={refresh}>
475
+ Refresh
476
+ </Button>
477
+ </Space>
478
+ </Form.Item>
479
+ </Form>
480
+
481
+ <Table
482
+ rowKey="id"
483
+ loading={loading}
484
+ dataSource={runs}
485
+ columns={columns}
486
+ scroll={{ x: 'max-content' }}
487
+ pagination={{
488
+ current: page,
489
+ pageSize,
490
+ total,
491
+ showSizeChanger: true,
492
+ pageSizeOptions: [10, 20, 50, 100],
493
+ showTotal: (count) => `${count} run${count === 1 ? '' : 's'}`,
494
+ onChange: (nextPage, nextSize) => {
495
+ setPage(nextPage);
496
+ if (nextSize && nextSize !== pageSize) setPageSize(nextSize);
497
+ },
498
+ }}
499
+ locale={{
500
+ emptyText: <Empty description={hasFilters ? 'No runs match the current filters' : 'No agent runs yet'} />,
501
+ }}
502
+ />
503
+ </Card>
504
+
505
+ <Drawer
506
+ title="Agent Run Detail"
507
+ width={980}
508
+ onClose={() => {
509
+ setSelectedRun(null);
510
+ setDetail(null);
511
+ }}
512
+ open={!!selectedRun}
513
+ >
514
+ {run && (
515
+ <Spin spinning={detailLoading}>
516
+ <Space direction="vertical" size={16} style={{ width: '100%' }}>
517
+ <Space wrap>
518
+ <Button icon={<ReloadOutlined />} onClick={() => fetchDetail(run.id)} loading={detailLoading}>
519
+ Refresh
520
+ </Button>
521
+ {run.status === 'waiting_user' && (
522
+ <Button
523
+ type="primary"
524
+ icon={<PlayCircleOutlined />}
525
+ loading={actionLoading}
526
+ onClick={() => runAction('resume', run.id)}
527
+ >
528
+ Resume
529
+ </Button>
530
+ )}
531
+ {!terminalRunStatuses.has(run.status) && (
532
+ <Popconfirm title="Cancel this run?" onConfirm={() => runAction('cancel', run.id)}>
533
+ <Button danger icon={<StopOutlined />} loading={actionLoading}>
534
+ Cancel run
535
+ </Button>
536
+ </Popconfirm>
537
+ )}
538
+ </Space>
539
+
540
+ <Descriptions bordered size="small" column={2}>
541
+ <Descriptions.Item label="Status">
542
+ <StatusTag status={run.status} />
543
+ </Descriptions.Item>
544
+ <Descriptions.Item label="Leader">
545
+ <Tag color="blue">{employeeMap.get(run.leaderUsername) || run.leaderUsername || '-'}</Tag>
546
+ </Descriptions.Item>
547
+ <Descriptions.Item label="Root run">
548
+ <Text code>{run.rootRunId || '-'}</Text>
549
+ </Descriptions.Item>
550
+ <Descriptions.Item label="Current step">
551
+ <Text code>{run.currentStepId || '-'}</Text>
552
+ </Descriptions.Item>
553
+ <Descriptions.Item label="Iterations">{run.iterationCount || 0}</Descriptions.Item>
554
+ <Descriptions.Item label="Approval">{run.approvalStatus || '-'}</Descriptions.Item>
555
+ <Descriptions.Item label="Plan version">{run.planVersion || '-'}</Descriptions.Item>
556
+ <Descriptions.Item label="Harness">{run.metadata?.harnessTag || '-'}</Descriptions.Item>
557
+ <Descriptions.Item label="Duration">
558
+ {formatDuration(run.startedAt || run.createdAt, run.endedAt)}
559
+ </Descriptions.Item>
560
+ <Descriptions.Item label="Started">{formatDate(run.startedAt || run.createdAt)}</Descriptions.Item>
561
+ <Descriptions.Item label="Ended">{formatDate(run.endedAt)}</Descriptions.Item>
562
+ </Descriptions>
563
+
564
+ <Card title="Goal" size="small">
565
+ <TextBlock value={run.goal} rows={6} />
566
+ </Card>
567
+
568
+ <Card title="Plan" size="small" extra={<BranchesOutlined />}>
569
+ <Table
570
+ rowKey="id"
571
+ size="small"
572
+ dataSource={steps}
573
+ columns={stepColumns}
574
+ pagination={false}
575
+ scroll={{ x: 'max-content' }}
576
+ expandable={{
577
+ expandedRowRender: (record: any) => (
578
+ <Space direction="vertical" size={12} style={{ width: '100%' }}>
579
+ <Card size="small" title="Input">
580
+ <TextBlock value={record.input} rows={8} />
581
+ </Card>
582
+ <Card size="small" title="Output">
583
+ <TextBlock value={record.output} rows={8} />
584
+ </Card>
585
+ {record.approval && Object.keys(record.approval).length > 0 && (
586
+ <Card size="small" title="Approval">
587
+ <TextBlock value={record.approval} rows={8} />
588
+ </Card>
589
+ )}
590
+ {record.error && (
591
+ <Card size="small" title="Error" style={{ borderColor: '#ffa39e' }}>
592
+ <TextBlock value={record.error} rows={8} />
593
+ </Card>
594
+ )}
595
+ </Space>
596
+ ),
597
+ }}
598
+ locale={{ emptyText: <Empty description="No plan steps" /> }}
599
+ />
600
+ </Card>
601
+
602
+ <Card title="Event Timeline" size="small">
603
+ {events.length ? (
604
+ <Timeline
605
+ items={events.map((event: any) => ({
606
+ key: event.id,
607
+ color: timelineColor(event.status),
608
+ children: (
609
+ <Space direction="vertical" size={2} style={{ width: '100%' }}>
610
+ <Space wrap>
611
+ <Text strong>{event.title || event.type}</Text>
612
+ <StatusTag status={event.status} />
613
+ {event.stepId && <Text type="secondary">step #{event.stepId}</Text>}
614
+ </Space>
615
+ <Text type="secondary">{formatDate(event.createdAt)}</Text>
616
+ {event.content && <TextBlock value={event.content} rows={4} />}
617
+ </Space>
618
+ ),
619
+ }))}
620
+ />
621
+ ) : (
622
+ <Empty description="No events captured" />
623
+ )}
624
+ </Card>
625
+
626
+ <Collapse
627
+ items={[
628
+ {
629
+ key: 'spans',
630
+ label: `Linked spans (${spans.length})`,
631
+ children: spans.length ? (
632
+ <Table
633
+ rowKey="id"
634
+ size="small"
635
+ dataSource={spans}
636
+ pagination={false}
637
+ scroll={{ x: 'max-content' }}
638
+ columns={[
639
+ {
640
+ title: 'Type',
641
+ dataIndex: 'type',
642
+ key: 'type',
643
+ width: 110,
644
+ render: (value: string) => <Tag>{value}</Tag>,
645
+ },
646
+ {
647
+ title: 'Status',
648
+ dataIndex: 'status',
649
+ key: 'status',
650
+ width: 110,
651
+ render: (value: string) => <StatusTag status={value} />,
652
+ },
653
+ { title: 'Title', dataIndex: 'title', key: 'title' },
654
+ {
655
+ title: 'Tool',
656
+ dataIndex: 'toolName',
657
+ key: 'toolName',
658
+ render: (value: string) => (value ? <Text code>{value}</Text> : '-'),
659
+ },
660
+ {
661
+ title: 'Duration',
662
+ dataIndex: 'durationMs',
663
+ key: 'durationMs',
664
+ width: 100,
665
+ render: (value: number) => (value ? `${value}ms` : '-'),
666
+ },
667
+ {
668
+ title: 'Skill Exec',
669
+ dataIndex: 'skillExecutionId',
670
+ key: 'skillExecutionId',
671
+ width: 110,
672
+ render: (value: any) => value || '-',
673
+ },
674
+ ]}
675
+ />
676
+ ) : (
677
+ <Empty description="No linked spans" />
678
+ ),
679
+ },
680
+ {
681
+ key: 'skills',
682
+ label: `Skill executions (${skillExecutions.length})`,
683
+ children: skillExecutions.length ? (
684
+ <Space direction="vertical" size={12} style={{ width: '100%' }}>
685
+ {skillExecutions.map((execution: any) => {
686
+ const files = parseJsonText<any[]>(execution.outputFiles, []);
687
+ return (
688
+ <Card
689
+ key={execution.id}
690
+ size="small"
691
+ title={
692
+ <Space wrap>
693
+ <Text>Skill execution #{execution.id}</Text>
694
+ <StatusTag status={execution.status} />
695
+ {execution.agentLoopStepId && (
696
+ <Text type="secondary">step #{execution.agentLoopStepId}</Text>
697
+ )}
698
+ </Space>
699
+ }
700
+ >
701
+ <Space direction="vertical" size={8} style={{ width: '100%' }}>
702
+ <Descriptions size="small" column={2}>
703
+ <Descriptions.Item label="Duration">
704
+ {execution.durationMs ? `${execution.durationMs}ms` : '-'}
705
+ </Descriptions.Item>
706
+ <Descriptions.Item label="Created">
707
+ {formatDate(execution.createdAt)}
708
+ </Descriptions.Item>
709
+ </Descriptions>
710
+ <Collapse
711
+ size="small"
712
+ items={[
713
+ {
714
+ key: 'stdout',
715
+ label: 'stdout',
716
+ children: <TextBlock value={execution.stdout} rows={10} />,
717
+ },
718
+ {
719
+ key: 'stderr',
720
+ label: 'stderr',
721
+ children: <TextBlock value={execution.stderr} rows={10} />,
722
+ },
723
+ {
724
+ key: 'files',
725
+ label: `files (${files.length})`,
726
+ children: files.length ? (
727
+ <Space direction="vertical">
728
+ {files.map((file: any, index: number) =>
729
+ renderSkillFileLink(execution, file, index),
730
+ )}
731
+ </Space>
732
+ ) : (
733
+ <Empty description="No files" />
734
+ ),
735
+ },
736
+ ]}
737
+ />
738
+ </Space>
739
+ </Card>
740
+ );
741
+ })}
742
+ </Space>
743
+ ) : (
744
+ <Empty description="No linked skill executions" />
745
+ ),
746
+ },
747
+ ]}
748
+ />
749
+
750
+ {(run.finalAnswer || run.summary) && (
751
+ <Card title="Final Output" size="small">
752
+ {run.summary && (
753
+ <Paragraph style={{ whiteSpace: 'pre-wrap', marginBottom: 12 }}>
754
+ <Text strong>Summary: </Text>
755
+ {run.summary}
756
+ </Paragraph>
757
+ )}
758
+ <TextBlock value={run.finalAnswer} rows={16} />
759
+ </Card>
760
+ )}
761
+ </Space>
762
+ </Spin>
763
+ )}
764
+ </Drawer>
765
+ </div>
766
+ );
767
+ };