plugin-agent-orchestrator 1.0.28 → 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 (108) 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/plugin.js +81 -5
  33. package/dist/server/skill-hub/tasks/SkillExecutionTask.js +9 -3
  34. package/dist/server/tools/delegate-task.js +11 -589
  35. package/dist/server/utils/skill-settings.js +18 -1
  36. package/package.json +47 -49
  37. package/src/client/AIEmployeesContext.tsx +5 -18
  38. package/src/client/AgentRunsTab.tsx +2 -771
  39. package/src/client/HarnessProfilesTab.tsx +2 -257
  40. package/src/client/OrchestratorSettings.tsx +97 -106
  41. package/src/client/RulesTab.tsx +2 -788
  42. package/src/client/plugin.tsx +0 -2
  43. package/src/client/skill-hub/components/ExecutionHistory.tsx +200 -202
  44. package/src/client/skill-hub/components/ExecutionProgress.tsx +51 -55
  45. package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
  46. package/src/client/skill-hub/components/SkillEditor.tsx +43 -39
  47. package/src/client/skill-hub/components/SkillManager.tsx +194 -181
  48. package/src/client/skill-hub/components/SkillTestPanel.tsx +141 -145
  49. package/src/client/skill-hub/locale.ts +16 -16
  50. package/src/client/skill-hub/tools/SkillHubCard.tsx +104 -109
  51. package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
  52. package/src/client/skill-hub/utils/jsonFields.ts +7 -3
  53. package/src/client-v2/components/AIEmployeesContext.tsx +3 -16
  54. package/src/client-v2/components/AgentRunsTab.tsx +182 -455
  55. package/src/client-v2/components/HarnessProfilesTab.tsx +34 -31
  56. package/src/client-v2/components/RulesTab.tsx +2 -782
  57. package/src/client-v2/components/TracingTab.tsx +1 -1
  58. package/src/client-v2/hooks/useApiRequest.ts +8 -1
  59. package/src/client-v2/pages/RulesPage.tsx +2 -2
  60. package/src/client-v2/plugin.tsx +3 -3
  61. package/src/locale/en-US.json +94 -25
  62. package/src/locale/vi-VN.json +94 -25
  63. package/src/locale/zh-CN.json +94 -25
  64. package/src/server/__tests__/native-sub-agent-observer.test.ts +246 -0
  65. package/src/server/__tests__/skill-settings.test.ts +6 -6
  66. package/src/server/__tests__/smoke.test.ts +1 -0
  67. package/src/server/collections/agent-execution-spans.ts +37 -0
  68. package/src/server/collections/agent-harness-profiles.ts +59 -59
  69. package/src/server/collections/agent-loop-events.ts +71 -71
  70. package/src/server/collections/agent-loop-steps.ts +144 -144
  71. package/src/server/collections/agent-memory-contexts.ts +95 -0
  72. package/src/server/collections/orchestrator-logs.ts +4 -4
  73. package/src/server/collections/skill-definitions.ts +111 -111
  74. package/src/server/collections/skill-executions.ts +106 -106
  75. package/src/server/collections/skill-loop-configs.ts +65 -65
  76. package/src/server/migrations/20260423000000-add-progress-fields.ts +14 -14
  77. package/src/server/migrations/20260425000000-add-interaction-schema.ts +3 -1
  78. package/src/server/migrations/20260427000000-change-packages-to-text.ts +4 -2
  79. package/src/server/migrations/20260427000001-change-other-json-to-text.ts +9 -5
  80. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
  81. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +145 -142
  82. package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +2 -2
  83. package/src/server/migrations/20260621000000-native-policy-profile-defaults.ts +193 -0
  84. package/src/server/plugin.ts +151 -94
  85. package/src/server/resources/agent-monitor.ts +482 -0
  86. package/src/server/services/AgentHarness.ts +38 -623
  87. package/src/server/services/AgentMemoryContextService.ts +256 -0
  88. package/src/server/services/AgentPlanValidator.ts +73 -73
  89. package/src/server/services/ExecutionSpanService.ts +6 -2
  90. package/src/server/services/FileManager.ts +144 -144
  91. package/src/server/services/NativeSubAgentObserver.ts +507 -0
  92. package/src/server/services/SkillManager.ts +583 -583
  93. package/src/server/services/SkillRepositoryService.ts +5 -7
  94. package/src/server/services/TokenTracker.ts +3 -3
  95. package/src/server/services/WorkerEnvManager.ts +1 -2
  96. package/src/server/skill-hub/actions/git-import.ts +5 -7
  97. package/src/server/skill-hub/plugin.ts +89 -6
  98. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +470 -460
  99. package/src/server/skill-hub/utils/json-fields.ts +1 -1
  100. package/src/server/tools/delegate-task.ts +13 -847
  101. package/src/server/utils/skill-settings.ts +24 -6
  102. package/dist/client-v2/264.0533912e6c5ea2d7.js +0 -10
  103. package/dist/client-v2/418.5ae055abf141820e.js +0 -10
  104. package/dist/client-v2/619.d99d3c9e61c99064.js +0 -10
  105. package/dist/client-v2/892.72db4161511c8a16.js +0 -10
  106. package/dist/client-v2/926.87f660b670d85bcc.js +0 -10
  107. package/src/client/tools/PlanApprovalCard.tsx +0 -176
  108. package/src/client/tools/registerOrchestratorCards.ts +0 -17
@@ -9,7 +9,6 @@ import {
9
9
  Empty,
10
10
  Form,
11
11
  message,
12
- Popconfirm,
13
12
  Select,
14
13
  Space,
15
14
  Spin,
@@ -18,101 +17,41 @@ import {
18
17
  Timeline,
19
18
  Typography,
20
19
  } 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';
20
+ import { CheckCircleOutlined, CloseCircleOutlined, EyeOutlined, ReloadOutlined, SyncOutlined } from '@ant-design/icons';
33
21
  import { useApiClient as useAPIClient, useRequest } from '../hooks/useApiRequest';
34
22
  import { useAIEmployees } from './AIEmployeesContext';
35
- import { parseJsonText } from '../utils/jsonFields';
23
+ import { useT } from '../skill-hub/locale';
36
24
 
37
25
  const { Paragraph, Text } = Typography;
38
26
 
39
27
  type FilterState = {
40
28
  leader?: string;
29
+ subAgent?: string;
41
30
  status?: string;
42
31
  };
43
32
 
44
- const terminalRunStatuses = new Set(['succeeded', 'failed', 'rejected', 'canceled']);
45
-
46
33
  function statusColor(status?: string) {
47
34
  switch (status) {
48
- case 'succeeded':
49
35
  case 'success':
50
36
  return 'success';
51
- case 'failed':
52
37
  case 'error':
53
38
  return 'error';
54
- case 'waiting_user':
55
- case 'waiting_plan_approval':
56
- case 'needs_replan':
57
- return 'warning';
58
- case 'approved':
59
39
  case 'running':
60
- case 'planning':
61
40
  return 'processing';
62
- case 'rejected':
63
- case 'canceled':
64
- case 'skipped':
65
- return 'default';
66
41
  default:
67
42
  return 'default';
68
43
  }
69
44
  }
70
45
 
71
46
  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
- }
47
+ if (status === 'success') return <CheckCircleOutlined />;
48
+ if (status === 'error') return <CloseCircleOutlined />;
49
+ return undefined;
111
50
  }
112
51
 
113
52
  function StatusTag({ status }: { status?: string }) {
114
53
  return (
115
- <Tag icon={statusIcon(status)} color={statusColor(status)}>
54
+ <Tag color={statusColor(status)} icon={statusIcon(status)}>
116
55
  {status || '-'}
117
56
  </Tag>
118
57
  );
@@ -122,17 +61,14 @@ function formatDate(value?: string) {
122
61
  return value ? new Date(value).toLocaleString() : '-';
123
62
  }
124
63
 
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`;
64
+ function formatDurationMs(value?: number) {
65
+ if (!value) return '-';
66
+ if (value >= 60000) return `${Math.round(value / 60000)}m`;
67
+ if (value >= 1000) return `${(value / 1000).toFixed(1)}s`;
68
+ return `${value}ms`;
133
69
  }
134
70
 
135
- function formatJson(value: any) {
71
+ function formatJson(value: unknown) {
136
72
  if (value === undefined || value === null || value === '') return '';
137
73
  if (typeof value === 'string') return value;
138
74
  try {
@@ -142,25 +78,7 @@ function formatJson(value: any) {
142
78
  }
143
79
  }
144
80
 
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 }) {
81
+ function TextBlock({ value, rows = 8 }: { value: unknown; rows?: number }) {
164
82
  const text = formatJson(value);
165
83
  if (!text) return <Text type="secondary">-</Text>;
166
84
  return (
@@ -175,18 +93,19 @@ function TextBlock({ value, rows = 10 }: { value: any; rows?: number }) {
175
93
 
176
94
  export const AgentRunsTab: React.FC = () => {
177
95
  const api = useAPIClient();
96
+ const t = useT();
178
97
  const { employees, employeeMap } = useAIEmployees();
179
98
  const [filters, setFilters] = useState<FilterState>({});
180
99
  const [page, setPage] = useState(1);
181
100
  const [pageSize, setPageSize] = useState(20);
182
101
  const [selectedRun, setSelectedRun] = useState<any>(null);
183
- const [detail, setDetail] = useState<any>(null);
184
102
  const [detailLoading, setDetailLoading] = useState(false);
185
- const [actionLoading, setActionLoading] = useState(false);
103
+ const [syncLoading, setSyncLoading] = useState(false);
186
104
 
187
105
  const requestParams = useMemo(() => {
188
- const filter: any = {};
106
+ const filter: Record<string, string> = {};
189
107
  if (filters.leader) filter.leaderUsername = filters.leader;
108
+ if (filters.subAgent) filter.subAgentUsername = filters.subAgent;
190
109
  if (filters.status) filter.status = filters.status;
191
110
  return {
192
111
  sort: ['-createdAt'],
@@ -198,7 +117,7 @@ export const AgentRunsTab: React.FC = () => {
198
117
 
199
118
  const { data, loading, refresh } = useRequest(
200
119
  {
201
- url: 'agentLoops:list',
120
+ url: 'agentMonitor:list',
202
121
  params: requestParams,
203
122
  },
204
123
  {
@@ -225,20 +144,6 @@ export const AgentRunsTab: React.FC = () => {
225
144
  [employees],
226
145
  );
227
146
 
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
147
  const updateFilter = (patch: Partial<FilterState>) => {
243
148
  setFilters((prev) => ({ ...prev, ...patch }));
244
149
  setPage(1);
@@ -249,230 +154,175 @@ export const AgentRunsTab: React.FC = () => {
249
154
  setPage(1);
250
155
  };
251
156
 
252
- const runAction = async (action: 'cancel' | 'resume', runId: string | number) => {
253
- setActionLoading(true);
157
+ const fetchDetail = async (record: any) => {
158
+ setSelectedRun(record);
159
+ setDetailLoading(true);
254
160
  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 },
161
+ const res = await api.request({
162
+ url: 'agentMonitor:get',
163
+ params: { filterByTk: record.id },
259
164
  });
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`);
165
+ setSelectedRun((res as any)?.data?.data?.data || (res as any)?.data?.data || record);
265
166
  } finally {
266
- setActionLoading(false);
167
+ setDetailLoading(false);
267
168
  }
268
169
  };
269
170
 
270
- const retryStep = async (stepId: string | number) => {
271
- const runId = detail?.run?.id || selectedRun?.id;
272
- setActionLoading(true);
171
+ const syncNativeRuns = async () => {
172
+ setSyncLoading(true);
273
173
  try {
274
- await api.request({
275
- url: 'agentLoops:retryStep',
276
- method: 'POST',
277
- data: { stepId },
174
+ const res = await api.request({
175
+ url: 'agentMonitor:sync',
176
+ method: 'post',
177
+ data: { limit: 500 },
278
178
  });
279
- message.success('Step queued for retry');
179
+ const result = (res as any)?.data?.data || {};
180
+ message.success(t('Synced {{count}} native runs', { count: result.created || 0 }));
280
181
  refresh();
281
- if (runId) await fetchDetail(runId);
282
182
  } catch (error: any) {
283
- message.error(error?.message || 'Failed to retry step');
183
+ const text = error?.response?.data?.errors?.[0]?.message || error?.message || t('Sync failed');
184
+ message.error(text);
284
185
  } finally {
285
- setActionLoading(false);
186
+ setSyncLoading(false);
286
187
  }
287
188
  };
288
189
 
190
+ const hasFilters = Boolean(filters.leader || filters.subAgent || filters.status);
191
+ const trace = Array.isArray(selectedRun?.trace) ? selectedRun.trace : [];
192
+ const toolMessages = Array.isArray(selectedRun?.toolMessages) ? selectedRun.toolMessages : [];
193
+ const nativeMessages = Array.isArray(selectedRun?.nativeMessages) ? selectedRun.nativeMessages : [];
194
+
289
195
  const columns = [
290
196
  {
291
- title: 'Time',
197
+ title: t('Time'),
292
198
  dataIndex: 'createdAt',
293
199
  key: 'createdAt',
294
200
  width: 170,
295
201
  render: formatDate,
296
202
  },
297
203
  {
298
- title: 'Leader',
204
+ title: t('Leader'),
299
205
  dataIndex: 'leaderUsername',
300
206
  key: 'leaderUsername',
301
207
  width: 160,
302
208
  render: (username: string) => <Tag color="blue">{employeeMap.get(username) || username || '-'}</Tag>,
303
209
  },
304
210
  {
305
- title: 'Goal',
306
- dataIndex: 'goal',
307
- key: 'goal',
308
- render: (goal: string) => (
309
- <Text ellipsis style={{ maxWidth: 380 }}>
310
- {goal || '-'}
211
+ title: t('Sub-Agent'),
212
+ dataIndex: 'subAgentUsername',
213
+ key: 'subAgentUsername',
214
+ width: 160,
215
+ render: (username: string) => <Tag color="green">{employeeMap.get(username) || username || '-'}</Tag>,
216
+ },
217
+ {
218
+ title: t('Task'),
219
+ dataIndex: 'task',
220
+ key: 'task',
221
+ render: (task: string) => (
222
+ <Text ellipsis style={{ maxWidth: 360 }}>
223
+ {task || '-'}
311
224
  </Text>
312
225
  ),
313
226
  },
314
227
  {
315
- title: 'Status',
228
+ title: t('Status'),
316
229
  dataIndex: 'status',
317
230
  key: 'status',
318
- width: 130,
231
+ width: 110,
319
232
  render: (status: string) => <StatusTag status={status} />,
320
233
  },
321
234
  {
322
- title: 'Iterations',
323
- dataIndex: 'iterationCount',
324
- key: 'iterationCount',
325
- width: 90,
326
- render: (value: number) => value ?? 0,
235
+ title: t('Duration'),
236
+ dataIndex: 'durationMs',
237
+ key: 'durationMs',
238
+ width: 100,
239
+ render: formatDurationMs,
327
240
  },
328
241
  {
329
- title: 'Duration',
330
- key: 'duration',
242
+ title: t('Context'),
243
+ dataIndex: 'memoryContextApplied',
244
+ key: 'memoryContextApplied',
331
245
  width: 100,
332
- render: (_: any, record: any) => formatDuration(record.startedAt || record.createdAt, record.endedAt),
246
+ render: (applied: boolean) => (applied ? <Tag color="purple">memory</Tag> : <Text type="secondary">-</Text>),
333
247
  },
334
248
  {
335
249
  title: '',
336
250
  key: 'actions',
337
251
  width: 90,
338
- render: (_: any, record: any) => (
339
- <Button type="link" size="small" icon={<EyeOutlined />} onClick={() => fetchDetail(record.id, record)}>
340
- Detail
252
+ render: (_: unknown, record: any) => (
253
+ <Button type="link" size="small" icon={<EyeOutlined />} onClick={() => fetchDetail(record)}>
254
+ {t('Detail')}
341
255
  </Button>
342
256
  ),
343
257
  },
344
258
  ];
345
259
 
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
260
  return (
420
261
  <div>
421
262
  <Alert
422
263
  type="info"
423
264
  showIcon
424
265
  style={{ marginBottom: 16 }}
425
- message="Agent Runs"
266
+ message={t('Native Agent Runs')}
426
267
  description={
427
268
  <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.
269
+ {t(
270
+ 'Native plugin-ai sub-agent dispatches captured by the orchestrator observer. Execution still runs through AIEmployee/SubAgentsDispatcher.',
271
+ )}
430
272
  </Text>
431
273
  }
432
274
  />
433
275
 
434
276
  <Card bordered={false}>
435
277
  <Form layout="inline" style={{ marginBottom: 16, rowGap: 8, flexWrap: 'wrap' }}>
436
- <Form.Item label="Leader">
278
+ <Form.Item label={t('Leader')}>
437
279
  <Select
438
280
  allowClear
439
281
  showSearch
440
282
  optionFilterProp="label"
441
- placeholder="Any leader"
283
+ placeholder={t('Any leader')}
442
284
  style={{ minWidth: 180 }}
443
285
  options={employeeOptions}
444
286
  value={filters.leader}
445
287
  onChange={(value) => updateFilter({ leader: value })}
446
288
  />
447
289
  </Form.Item>
448
- <Form.Item label="Status">
290
+ <Form.Item label={t('Sub-Agent')}>
449
291
  <Select
450
292
  allowClear
451
- placeholder="Any status"
452
- style={{ minWidth: 160 }}
293
+ showSearch
294
+ optionFilterProp="label"
295
+ placeholder={t('Any sub-agent')}
296
+ style={{ minWidth: 180 }}
297
+ options={employeeOptions}
298
+ value={filters.subAgent}
299
+ onChange={(value) => updateFilter({ subAgent: value })}
300
+ />
301
+ </Form.Item>
302
+ <Form.Item label={t('Status')}>
303
+ <Select
304
+ allowClear
305
+ placeholder={t('Any status')}
306
+ style={{ minWidth: 140 }}
453
307
  value={filters.status}
454
308
  onChange={(value) => updateFilter({ status: value })}
455
309
  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' },
310
+ { label: t('Running'), value: 'running' },
311
+ { label: t('Success'), value: 'success' },
312
+ { label: t('Error'), value: 'error' },
466
313
  ]}
467
314
  />
468
315
  </Form.Item>
469
316
  <Form.Item>
470
317
  <Space>
471
318
  <Button onClick={resetFilters} disabled={!hasFilters}>
472
- Reset
319
+ {t('Reset')}
473
320
  </Button>
474
321
  <Button icon={<ReloadOutlined />} onClick={refresh}>
475
- Refresh
322
+ {t('Refresh')}
323
+ </Button>
324
+ <Button icon={<SyncOutlined />} onClick={syncNativeRuns} loading={syncLoading}>
325
+ {t('Sync')}
476
326
  </Button>
477
327
  </Space>
478
328
  </Form.Item>
@@ -490,272 +340,149 @@ export const AgentRunsTab: React.FC = () => {
490
340
  total,
491
341
  showSizeChanger: true,
492
342
  pageSizeOptions: [10, 20, 50, 100],
493
- showTotal: (count) => `${count} run${count === 1 ? '' : 's'}`,
343
+ showTotal: (count) => t('{{count}} runs', { count }),
494
344
  onChange: (nextPage, nextSize) => {
495
345
  setPage(nextPage);
496
346
  if (nextSize && nextSize !== pageSize) setPageSize(nextSize);
497
347
  },
498
348
  }}
499
349
  locale={{
500
- emptyText: <Empty description={hasFilters ? 'No runs match the current filters' : 'No agent runs yet'} />,
350
+ emptyText: (
351
+ <Empty description={hasFilters ? t('No runs match the current filters') : t('No native runs yet')} />
352
+ ),
501
353
  }}
502
354
  />
503
355
  </Card>
504
356
 
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 && (
357
+ <Drawer title={t('Native Run Detail')} width={960} onClose={() => setSelectedRun(null)} open={!!selectedRun}>
358
+ {selectedRun && (
515
359
  <Spin spinning={detailLoading}>
516
360
  <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
361
  <Descriptions bordered size="small" column={2}>
541
- <Descriptions.Item label="Status">
542
- <StatusTag status={run.status} />
362
+ <Descriptions.Item label={t('Status')}>
363
+ <StatusTag status={selectedRun.status} />
364
+ </Descriptions.Item>
365
+ <Descriptions.Item label={t('Harness')}>{selectedRun.harnessTag || 'default'}</Descriptions.Item>
366
+ <Descriptions.Item label={t('Leader')}>
367
+ <Tag color="blue">
368
+ {employeeMap.get(selectedRun.leaderUsername) || selectedRun.leaderUsername || '-'}
369
+ </Tag>
370
+ </Descriptions.Item>
371
+ <Descriptions.Item label={t('Sub-Agent')}>
372
+ <Tag color="green">
373
+ {employeeMap.get(selectedRun.subAgentUsername) || selectedRun.subAgentUsername || '-'}
374
+ </Tag>
543
375
  </Descriptions.Item>
544
- <Descriptions.Item label="Leader">
545
- <Tag color="blue">{employeeMap.get(run.leaderUsername) || run.leaderUsername || '-'}</Tag>
376
+ <Descriptions.Item label={t('Parent session')}>
377
+ <Text code>{selectedRun.parentSessionId || '-'}</Text>
546
378
  </Descriptions.Item>
547
- <Descriptions.Item label="Root run">
548
- <Text code>{run.rootRunId || '-'}</Text>
379
+ <Descriptions.Item label={t('Sub session')}>
380
+ <Text code>{selectedRun.subSessionId || '-'}</Text>
549
381
  </Descriptions.Item>
550
- <Descriptions.Item label="Current step">
551
- <Text code>{run.currentStepId || '-'}</Text>
382
+ <Descriptions.Item label={t('Tool call')}>
383
+ <Text code>{selectedRun.toolCallId || '-'}</Text>
552
384
  </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)}
385
+ <Descriptions.Item label={t('Duration')}>{formatDurationMs(selectedRun.durationMs)}</Descriptions.Item>
386
+ <Descriptions.Item label={t('Started')}>
387
+ {formatDate(selectedRun.startedAt || selectedRun.createdAt)}
559
388
  </Descriptions.Item>
560
- <Descriptions.Item label="Started">{formatDate(run.startedAt || run.createdAt)}</Descriptions.Item>
561
- <Descriptions.Item label="Ended">{formatDate(run.endedAt)}</Descriptions.Item>
389
+ <Descriptions.Item label={t('Ended')}>{formatDate(selectedRun.endedAt)}</Descriptions.Item>
562
390
  </Descriptions>
563
391
 
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
- />
392
+ <Card title={t('Task')} size="small">
393
+ <TextBlock value={selectedRun.task || selectedRun.input?.question} rows={6} />
600
394
  </Card>
601
395
 
602
- <Card title="Event Timeline" size="small">
603
- {events.length ? (
396
+ <Card title={t('Execution Flow')} size="small">
397
+ {trace.length ? (
604
398
  <Timeline
605
- items={events.map((event: any) => ({
606
- key: event.id,
607
- color: timelineColor(event.status),
399
+ items={trace.map((item: any) => ({
400
+ key: item.id,
401
+ color: item.status === 'error' ? 'red' : item.status === 'running' ? 'blue' : 'green',
608
402
  children: (
609
403
  <Space direction="vertical" size={2} style={{ width: '100%' }}>
610
404
  <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>}
405
+ <Text strong>{item.title || item.type}</Text>
406
+ <Tag>{item.type}</Tag>
407
+ <StatusTag status={item.status} />
408
+ {item.toolName && <Text code>{item.toolName}</Text>}
409
+ {item.durationMs ? <Text type="secondary">{formatDurationMs(item.durationMs)}</Text> : null}
614
410
  </Space>
615
- <Text type="secondary">{formatDate(event.createdAt)}</Text>
616
- {event.content && <TextBlock value={event.content} rows={4} />}
411
+ <Text type="secondary">{formatDate(item.at)}</Text>
412
+ {item.content ? <TextBlock value={item.content} rows={4} /> : null}
617
413
  </Space>
618
414
  ),
619
415
  }))}
620
416
  />
621
417
  ) : (
622
- <Empty description="No events captured" />
418
+ <Empty description={t('No execution flow captured')} />
623
419
  )}
624
420
  </Card>
625
421
 
626
422
  <Collapse
627
423
  items={[
628
424
  {
629
- key: 'spans',
630
- label: `Linked spans (${spans.length})`,
631
- children: spans.length ? (
425
+ key: 'toolMessages',
426
+ label: t('Native tool messages ({{count}})', { count: toolMessages.length }),
427
+ children: toolMessages.length ? (
632
428
  <Table
633
- rowKey="id"
429
+ rowKey={(record: any) => `${record.sessionId}:${record.toolCallId}`}
634
430
  size="small"
635
- dataSource={spans}
636
431
  pagination={false}
432
+ dataSource={toolMessages}
637
433
  scroll={{ x: 'max-content' }}
638
434
  columns={[
435
+ { title: t('Session'), dataIndex: 'sessionId', key: 'sessionId' },
436
+ { title: t('Tool'), dataIndex: 'toolName', key: 'toolName' },
639
437
  {
640
- title: 'Type',
641
- dataIndex: 'type',
642
- key: 'type',
643
- width: 110,
644
- render: (value: string) => <Tag>{value}</Tag>,
645
- },
646
- {
647
- title: 'Status',
438
+ title: t('Status'),
648
439
  dataIndex: 'status',
649
440
  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` : '-'),
441
+ render: (value: string) => <StatusTag status={value === 'error' ? 'error' : value} />,
666
442
  },
443
+ { title: t('Invoke'), dataIndex: 'invokeStatus', key: 'invokeStatus' },
667
444
  {
668
- title: 'Skill Exec',
669
- dataIndex: 'skillExecutionId',
670
- key: 'skillExecutionId',
671
- width: 110,
672
- render: (value: any) => value || '-',
445
+ title: t('Content'),
446
+ dataIndex: 'content',
447
+ key: 'content',
448
+ render: (value: unknown) => <TextBlock value={value} rows={3} />,
673
449
  },
674
450
  ]}
675
451
  />
676
452
  ) : (
677
- <Empty description="No linked spans" />
453
+ <Empty description={t('No native tool messages')} />
678
454
  ),
679
455
  },
680
456
  {
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
- })}
457
+ key: 'messages',
458
+ label: t('Native messages ({{count}})', { count: nativeMessages.length }),
459
+ children: nativeMessages.length ? (
460
+ <Space direction="vertical" size={8} style={{ width: '100%' }}>
461
+ {nativeMessages.map((item: any) => (
462
+ <Card
463
+ key={`${item.sessionId}:${item.messageId}`}
464
+ size="small"
465
+ title={`${item.role} #${item.messageId}`}
466
+ >
467
+ <TextBlock value={item.content || item.metadata} rows={6} />
468
+ </Card>
469
+ ))}
742
470
  </Space>
743
471
  ) : (
744
- <Empty description="No linked skill executions" />
472
+ <Empty description={t('No native messages loaded')} />
745
473
  ),
746
474
  },
475
+ {
476
+ key: 'metadata',
477
+ label: t('Monitor metadata'),
478
+ children: <TextBlock value={selectedRun.metadata} rows={10} />,
479
+ },
747
480
  ]}
748
481
  />
749
482
 
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} />
483
+ {(selectedRun.output || selectedRun.error) && (
484
+ <Card title={selectedRun.error ? t('Error') : t('Result')} size="small">
485
+ <TextBlock value={selectedRun.error || selectedRun.output} rows={12} />
759
486
  </Card>
760
487
  )}
761
488
  </Space>