plugin-agent-orchestrator 1.0.27 → 1.0.32

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