plugin-agent-orchestrator 1.0.5 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/index.js +1 -1
- package/dist/externalVersion.js +6 -6
- package/dist/server/collections/orchestrator-config.js +10 -0
- package/dist/server/collections/orchestrator-logs.js +19 -2
- package/dist/server/migrations/20260427000000-add-tracing-detail-fields.d.ts +7 -0
- package/dist/server/migrations/20260427000000-add-tracing-detail-fields.js +62 -0
- package/dist/server/migrations/20260429000000-add-llm-fields.d.ts +7 -0
- package/dist/server/migrations/20260429000000-add-llm-fields.js +60 -0
- package/dist/server/resources/tracing.js +8 -3
- package/dist/server/tools/delegate-task.js +314 -100
- package/package.json +1 -1
- package/src/client/RulesTab.tsx +134 -8
- package/src/client/TracingTab.tsx +171 -21
- package/src/server/collections/orchestrator-config.ts +10 -0
- package/src/server/collections/orchestrator-logs.ts +19 -2
- package/src/server/migrations/20260427000000-add-tracing-detail-fields.ts +41 -0
- package/src/server/migrations/20260429000000-add-llm-fields.ts +37 -0
- package/src/server/resources/tracing.ts +6 -2
- package/src/server/tools/delegate-task.ts +379 -109
package/src/client/RulesTab.tsx
CHANGED
|
@@ -13,6 +13,10 @@ import {
|
|
|
13
13
|
Tag,
|
|
14
14
|
Typography,
|
|
15
15
|
Alert,
|
|
16
|
+
Collapse,
|
|
17
|
+
Empty,
|
|
18
|
+
Input,
|
|
19
|
+
Select,
|
|
16
20
|
} from 'antd';
|
|
17
21
|
import { PlusOutlined, EditOutlined, DeleteOutlined, SwapRightOutlined } from '@ant-design/icons';
|
|
18
22
|
import { useAPIClient, useRequest } from '@nocobase/client';
|
|
@@ -34,8 +38,39 @@ export const RulesTab: React.FC = () => {
|
|
|
34
38
|
},
|
|
35
39
|
});
|
|
36
40
|
|
|
41
|
+
const { data: llmServicesData, loading: llmLoading } = useRequest({
|
|
42
|
+
url: 'llmServices:list',
|
|
43
|
+
params: {
|
|
44
|
+
filter: { enabled: true },
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const llmServices = React.useMemo(() => {
|
|
49
|
+
return (llmServicesData as any)?.data || [];
|
|
50
|
+
}, [llmServicesData]);
|
|
51
|
+
|
|
37
52
|
// P3 FIX: Use shared context instead of duplicate API call
|
|
38
53
|
const { employeeMap } = useAIEmployees();
|
|
54
|
+
const rules = React.useMemo(() => {
|
|
55
|
+
const rows = (data as any)?.data;
|
|
56
|
+
return Array.isArray(rows) ? rows : [];
|
|
57
|
+
}, [data]);
|
|
58
|
+
|
|
59
|
+
const groupedRules = React.useMemo(() => {
|
|
60
|
+
const groups = new Map<string, any[]>();
|
|
61
|
+
for (const rule of rules) {
|
|
62
|
+
const key = rule.leaderUsername || 'unknown';
|
|
63
|
+
if (!groups.has(key)) {
|
|
64
|
+
groups.set(key, []);
|
|
65
|
+
}
|
|
66
|
+
groups.get(key)!.push(rule);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return Array.from(groups.entries()).map(([leaderUsername, items]) => ({
|
|
70
|
+
leaderUsername,
|
|
71
|
+
items,
|
|
72
|
+
}));
|
|
73
|
+
}, [rules]);
|
|
39
74
|
|
|
40
75
|
const handleOpen = (record?: any) => {
|
|
41
76
|
setEditingRecord(record);
|
|
@@ -136,6 +171,24 @@ export const RulesTab: React.FC = () => {
|
|
|
136
171
|
width: 100,
|
|
137
172
|
render: (v: number) => `${((v ?? 120000) / 1000).toFixed(0)}s`,
|
|
138
173
|
},
|
|
174
|
+
{
|
|
175
|
+
title: 'LLM Override',
|
|
176
|
+
key: 'llmOverride',
|
|
177
|
+
width: 140,
|
|
178
|
+
render: (_: any, record: any) => {
|
|
179
|
+
if (record.llmService && record.model) {
|
|
180
|
+
const svc = llmServices.find((s: any) => s.name === record.llmService);
|
|
181
|
+
const svcName = svc ? svc.title : record.llmService;
|
|
182
|
+
return (
|
|
183
|
+
<Space direction="vertical" size={0}>
|
|
184
|
+
<Text style={{ fontSize: 12 }}>{svcName}</Text>
|
|
185
|
+
<Text type="secondary" style={{ fontSize: 12 }}>{record.model}</Text>
|
|
186
|
+
</Space>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
return <Text type="secondary" style={{ fontSize: 12 }}>Inherited</Text>;
|
|
190
|
+
},
|
|
191
|
+
},
|
|
139
192
|
{
|
|
140
193
|
title: 'Enabled',
|
|
141
194
|
dataIndex: 'enabled',
|
|
@@ -199,14 +252,41 @@ export const RulesTab: React.FC = () => {
|
|
|
199
252
|
New Rule
|
|
200
253
|
</Button>
|
|
201
254
|
</div>
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
255
|
+
{groupedRules.length ? (
|
|
256
|
+
<Collapse
|
|
257
|
+
bordered={false}
|
|
258
|
+
defaultActiveKey={groupedRules.map((group) => group.leaderUsername)}
|
|
259
|
+
items={groupedRules.map((group) => ({
|
|
260
|
+
key: group.leaderUsername,
|
|
261
|
+
label: (
|
|
262
|
+
<Space>
|
|
263
|
+
<Tag color="blue">{employeeMap.get(group.leaderUsername) || group.leaderUsername}</Tag>
|
|
264
|
+
<Text type="secondary">{group.items.length} sub-agent{group.items.length > 1 ? 's' : ''}</Text>
|
|
265
|
+
</Space>
|
|
266
|
+
),
|
|
267
|
+
children: (
|
|
268
|
+
<Table
|
|
269
|
+
rowKey="id"
|
|
270
|
+
loading={loading}
|
|
271
|
+
dataSource={group.items}
|
|
272
|
+
columns={columns}
|
|
273
|
+
pagination={false}
|
|
274
|
+
size="middle"
|
|
275
|
+
/>
|
|
276
|
+
),
|
|
277
|
+
}))}
|
|
278
|
+
/>
|
|
279
|
+
) : (
|
|
280
|
+
<Table
|
|
281
|
+
rowKey="id"
|
|
282
|
+
loading={loading}
|
|
283
|
+
dataSource={[]}
|
|
284
|
+
columns={columns}
|
|
285
|
+
pagination={false}
|
|
286
|
+
size="middle"
|
|
287
|
+
locale={{ emptyText: <Empty description="No orchestration rules yet" /> }}
|
|
288
|
+
/>
|
|
289
|
+
)}
|
|
210
290
|
</Card>
|
|
211
291
|
|
|
212
292
|
<Drawer
|
|
@@ -262,6 +342,52 @@ export const RulesTab: React.FC = () => {
|
|
|
262
342
|
<InputNumber min={10000} max={600000} step={10000} style={{ width: '100%' }} />
|
|
263
343
|
</Form.Item>
|
|
264
344
|
|
|
345
|
+
<Form.Item
|
|
346
|
+
name="llmService"
|
|
347
|
+
label="Override LLM Service"
|
|
348
|
+
tooltip="Optional: Provider name. Leave empty to inherit from Leader."
|
|
349
|
+
>
|
|
350
|
+
<Select
|
|
351
|
+
allowClear
|
|
352
|
+
placeholder="Inherit from Leader"
|
|
353
|
+
options={llmServices.map((svc: any) => ({
|
|
354
|
+
label: svc.title || svc.name,
|
|
355
|
+
value: svc.name,
|
|
356
|
+
}))}
|
|
357
|
+
onChange={() => form.setFieldValue('model', undefined)}
|
|
358
|
+
/>
|
|
359
|
+
</Form.Item>
|
|
360
|
+
|
|
361
|
+
<Form.Item
|
|
362
|
+
noStyle
|
|
363
|
+
shouldUpdate={(prevValues, currentValues) => prevValues.llmService !== currentValues.llmService}
|
|
364
|
+
>
|
|
365
|
+
{() => {
|
|
366
|
+
const selectedServiceId = form.getFieldValue('llmService');
|
|
367
|
+
const selectedService = llmServices.find((s: any) => s.name === selectedServiceId);
|
|
368
|
+
const availableModels = selectedService?.enabledModels?.models || [];
|
|
369
|
+
|
|
370
|
+
return (
|
|
371
|
+
<Form.Item
|
|
372
|
+
name="model"
|
|
373
|
+
label="Override Model"
|
|
374
|
+
tooltip="Optional: Model name. Leave empty to inherit from Leader."
|
|
375
|
+
rules={[{ required: !!selectedServiceId, message: 'Please select a model' }]}
|
|
376
|
+
>
|
|
377
|
+
<Select
|
|
378
|
+
allowClear
|
|
379
|
+
placeholder={selectedServiceId ? 'Select a model' : 'Inherit from Leader'}
|
|
380
|
+
disabled={!selectedServiceId}
|
|
381
|
+
options={availableModels.map((m: any) => ({
|
|
382
|
+
label: m.label,
|
|
383
|
+
value: m.value,
|
|
384
|
+
}))}
|
|
385
|
+
/>
|
|
386
|
+
</Form.Item>
|
|
387
|
+
);
|
|
388
|
+
}}
|
|
389
|
+
</Form.Item>
|
|
390
|
+
|
|
265
391
|
<Form.Item name="enabled" label="Enabled" valuePropName="checked">
|
|
266
392
|
<Switch />
|
|
267
393
|
</Form.Item>
|
|
@@ -9,19 +9,25 @@ import {
|
|
|
9
9
|
Alert,
|
|
10
10
|
Button,
|
|
11
11
|
Empty,
|
|
12
|
+
Space,
|
|
13
|
+
Timeline,
|
|
14
|
+
Collapse,
|
|
15
|
+
Spin,
|
|
12
16
|
} from 'antd';
|
|
13
17
|
import {
|
|
14
18
|
EyeOutlined,
|
|
15
19
|
CheckCircleOutlined,
|
|
16
20
|
CloseCircleOutlined,
|
|
17
21
|
} from '@ant-design/icons';
|
|
18
|
-
import { useRequest } from '@nocobase/client';
|
|
22
|
+
import { useAPIClient, useRequest } from '@nocobase/client';
|
|
19
23
|
import { useAIEmployees } from './AIEmployeesContext';
|
|
20
24
|
|
|
21
25
|
const { Text, Paragraph } = Typography;
|
|
22
26
|
|
|
23
27
|
export const TracingTab: React.FC = () => {
|
|
28
|
+
const api = useAPIClient();
|
|
24
29
|
const [selectedLog, setSelectedLog] = useState<any>(null);
|
|
30
|
+
const [detailLoading, setDetailLoading] = useState(false);
|
|
25
31
|
|
|
26
32
|
const { data, loading, refresh } = useRequest({
|
|
27
33
|
url: 'orchestratorTracing:list',
|
|
@@ -32,6 +38,48 @@ export const TracingTab: React.FC = () => {
|
|
|
32
38
|
});
|
|
33
39
|
|
|
34
40
|
const { employeeMap } = useAIEmployees();
|
|
41
|
+
const logs = React.useMemo(() => {
|
|
42
|
+
let rows = (data as any)?.data;
|
|
43
|
+
// Handle double-wrapped NocoBase response data
|
|
44
|
+
if (rows && !Array.isArray(rows) && Array.isArray(rows.data)) {
|
|
45
|
+
rows = rows.data;
|
|
46
|
+
}
|
|
47
|
+
return Array.isArray(rows) ? rows : Array.isArray(data) ? data : [];
|
|
48
|
+
}, [data]);
|
|
49
|
+
|
|
50
|
+
const groupedLogs = React.useMemo(() => {
|
|
51
|
+
const groups = new Map<string, any[]>();
|
|
52
|
+
for (const log of logs) {
|
|
53
|
+
const key = log.leaderUsername || 'unknown';
|
|
54
|
+
if (!groups.has(key)) {
|
|
55
|
+
groups.set(key, []);
|
|
56
|
+
}
|
|
57
|
+
groups.get(key)!.push(log);
|
|
58
|
+
}
|
|
59
|
+
return Array.from(groups.entries()).map(([leaderUsername, items]) => ({
|
|
60
|
+
leaderUsername,
|
|
61
|
+
items,
|
|
62
|
+
}));
|
|
63
|
+
}, [logs]);
|
|
64
|
+
|
|
65
|
+
const formatDuration = (ms: number) => {
|
|
66
|
+
if (!ms) return '-';
|
|
67
|
+
return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleOpenLog = async (record: any) => {
|
|
71
|
+
setSelectedLog(record);
|
|
72
|
+
setDetailLoading(true);
|
|
73
|
+
try {
|
|
74
|
+
const res = await api.request({
|
|
75
|
+
url: 'orchestratorTracing:get',
|
|
76
|
+
params: { filterByTk: record.id },
|
|
77
|
+
});
|
|
78
|
+
setSelectedLog((res as any)?.data?.data || (res as any)?.data || record);
|
|
79
|
+
} finally {
|
|
80
|
+
setDetailLoading(false);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
35
83
|
|
|
36
84
|
const columns = [
|
|
37
85
|
{
|
|
@@ -41,6 +89,14 @@ export const TracingTab: React.FC = () => {
|
|
|
41
89
|
width: 170,
|
|
42
90
|
render: (v: string) => v ? new Date(v).toLocaleString() : '-',
|
|
43
91
|
},
|
|
92
|
+
{
|
|
93
|
+
title: 'Leader',
|
|
94
|
+
dataIndex: 'leaderUsername',
|
|
95
|
+
key: 'leaderUsername',
|
|
96
|
+
render: (username: string) => (
|
|
97
|
+
<Tag color="blue">{employeeMap.get(username) || username}</Tag>
|
|
98
|
+
),
|
|
99
|
+
},
|
|
44
100
|
{
|
|
45
101
|
title: 'Sub-Agent',
|
|
46
102
|
dataIndex: 'subAgentUsername',
|
|
@@ -78,10 +134,7 @@ export const TracingTab: React.FC = () => {
|
|
|
78
134
|
dataIndex: 'durationMs',
|
|
79
135
|
key: 'durationMs',
|
|
80
136
|
width: 90,
|
|
81
|
-
render:
|
|
82
|
-
if (!ms) return '-';
|
|
83
|
-
return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`;
|
|
84
|
-
},
|
|
137
|
+
render: formatDuration,
|
|
85
138
|
},
|
|
86
139
|
{
|
|
87
140
|
title: 'Depth',
|
|
@@ -99,7 +152,7 @@ export const TracingTab: React.FC = () => {
|
|
|
99
152
|
type="link"
|
|
100
153
|
size="small"
|
|
101
154
|
icon={<EyeOutlined />}
|
|
102
|
-
onClick={() =>
|
|
155
|
+
onClick={() => handleOpenLog(record)}
|
|
103
156
|
>
|
|
104
157
|
Detail
|
|
105
158
|
</Button>
|
|
@@ -126,24 +179,51 @@ export const TracingTab: React.FC = () => {
|
|
|
126
179
|
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'flex-end' }}>
|
|
127
180
|
<Button onClick={refresh}>Refresh</Button>
|
|
128
181
|
</div>
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
182
|
+
{groupedLogs.length ? (
|
|
183
|
+
<Collapse
|
|
184
|
+
bordered={false}
|
|
185
|
+
defaultActiveKey={groupedLogs.map((group) => group.leaderUsername)}
|
|
186
|
+
items={groupedLogs.map((group) => ({
|
|
187
|
+
key: group.leaderUsername,
|
|
188
|
+
label: (
|
|
189
|
+
<Space>
|
|
190
|
+
<Tag color="blue">{employeeMap.get(group.leaderUsername) || group.leaderUsername}</Tag>
|
|
191
|
+
<Text type="secondary">{group.items.length} execution{group.items.length > 1 ? 's' : ''}</Text>
|
|
192
|
+
</Space>
|
|
193
|
+
),
|
|
194
|
+
children: (
|
|
195
|
+
<Table
|
|
196
|
+
rowKey="id"
|
|
197
|
+
loading={loading}
|
|
198
|
+
dataSource={group.items}
|
|
199
|
+
columns={columns}
|
|
200
|
+
pagination={{ hideOnSinglePage: true, pageSize: 20 }}
|
|
201
|
+
size="middle"
|
|
202
|
+
/>
|
|
203
|
+
),
|
|
204
|
+
}))}
|
|
205
|
+
/>
|
|
206
|
+
) : (
|
|
207
|
+
<Table
|
|
208
|
+
rowKey="id"
|
|
209
|
+
loading={loading}
|
|
210
|
+
dataSource={[]}
|
|
211
|
+
columns={columns}
|
|
212
|
+
pagination={false}
|
|
213
|
+
size="middle"
|
|
214
|
+
locale={{ emptyText: <Empty description="No delegation executions yet" /> }}
|
|
215
|
+
/>
|
|
216
|
+
)}
|
|
138
217
|
</Card>
|
|
139
218
|
|
|
140
219
|
<Drawer
|
|
141
220
|
title="Delegation Detail"
|
|
142
|
-
width={
|
|
221
|
+
width={760}
|
|
143
222
|
onClose={() => setSelectedLog(null)}
|
|
144
223
|
open={!!selectedLog}
|
|
145
224
|
>
|
|
146
225
|
{selectedLog && (
|
|
226
|
+
<Spin spinning={detailLoading}>
|
|
147
227
|
<>
|
|
148
228
|
<Descriptions column={1} bordered size="small" style={{ marginBottom: 16 }}>
|
|
149
229
|
<Descriptions.Item label="Status">
|
|
@@ -154,6 +234,11 @@ export const TracingTab: React.FC = () => {
|
|
|
154
234
|
{selectedLog.status}
|
|
155
235
|
</Tag>
|
|
156
236
|
</Descriptions.Item>
|
|
237
|
+
<Descriptions.Item label="Leader">
|
|
238
|
+
<Tag color="blue">
|
|
239
|
+
{employeeMap.get(selectedLog.leaderUsername) || selectedLog.leaderUsername}
|
|
240
|
+
</Tag>
|
|
241
|
+
</Descriptions.Item>
|
|
157
242
|
<Descriptions.Item label="Sub-Agent">
|
|
158
243
|
<Tag color="green">
|
|
159
244
|
{employeeMap.get(selectedLog.subAgentUsername) || selectedLog.subAgentUsername}
|
|
@@ -166,11 +251,7 @@ export const TracingTab: React.FC = () => {
|
|
|
166
251
|
{selectedLog.depth ?? 0}
|
|
167
252
|
</Descriptions.Item>
|
|
168
253
|
<Descriptions.Item label="Duration">
|
|
169
|
-
{selectedLog.durationMs
|
|
170
|
-
? selectedLog.durationMs >= 1000
|
|
171
|
-
? `${(selectedLog.durationMs / 1000).toFixed(1)}s`
|
|
172
|
-
: `${selectedLog.durationMs}ms`
|
|
173
|
-
: '-'}
|
|
254
|
+
{formatDuration(selectedLog.durationMs)}
|
|
174
255
|
</Descriptions.Item>
|
|
175
256
|
<Descriptions.Item label="Time">
|
|
176
257
|
{selectedLog.createdAt ? new Date(selectedLog.createdAt).toLocaleString() : '-'}
|
|
@@ -183,6 +264,74 @@ export const TracingTab: React.FC = () => {
|
|
|
183
264
|
</Paragraph>
|
|
184
265
|
</Card>
|
|
185
266
|
|
|
267
|
+
{selectedLog.context && (
|
|
268
|
+
<Card title="Context" size="small" style={{ marginBottom: 16 }}>
|
|
269
|
+
<Paragraph style={{ whiteSpace: 'pre-wrap', margin: 0, fontSize: 13 }}>
|
|
270
|
+
{selectedLog.context}
|
|
271
|
+
</Paragraph>
|
|
272
|
+
</Card>
|
|
273
|
+
)}
|
|
274
|
+
|
|
275
|
+
<Card title="Sub-Agent Flow" size="small" style={{ marginBottom: 16 }}>
|
|
276
|
+
{Array.isArray(selectedLog.trace) && selectedLog.trace.length ? (
|
|
277
|
+
<Timeline
|
|
278
|
+
items={selectedLog.trace.map((item: any, index: number) => ({
|
|
279
|
+
key: index,
|
|
280
|
+
color: item.status === 'error' ? 'red' : item.type === 'tool_call' ? 'blue' : 'green',
|
|
281
|
+
children: (
|
|
282
|
+
<div>
|
|
283
|
+
<Space direction="vertical" size={2} style={{ width: '100%' }}>
|
|
284
|
+
<Text strong>{item.title || item.type}</Text>
|
|
285
|
+
<Text type="secondary">{item.at ? new Date(item.at).toLocaleString() : ''}</Text>
|
|
286
|
+
{item.toolName && <Text code>{item.toolName}</Text>}
|
|
287
|
+
{item.content && (
|
|
288
|
+
<Paragraph style={{ whiteSpace: 'pre-wrap', margin: 0, fontSize: 13 }}>
|
|
289
|
+
{item.content}
|
|
290
|
+
</Paragraph>
|
|
291
|
+
)}
|
|
292
|
+
{item.args && (
|
|
293
|
+
<Paragraph style={{ whiteSpace: 'pre-wrap', margin: 0, fontSize: 12 }}>
|
|
294
|
+
{JSON.stringify(item.args, null, 2)}
|
|
295
|
+
</Paragraph>
|
|
296
|
+
)}
|
|
297
|
+
</Space>
|
|
298
|
+
</div>
|
|
299
|
+
),
|
|
300
|
+
}))}
|
|
301
|
+
/>
|
|
302
|
+
) : (
|
|
303
|
+
<Empty description="No flow trace captured" />
|
|
304
|
+
)}
|
|
305
|
+
</Card>
|
|
306
|
+
|
|
307
|
+
{Array.isArray(selectedLog.messages) && selectedLog.messages.length > 0 && (
|
|
308
|
+
<Collapse
|
|
309
|
+
style={{ marginBottom: 16 }}
|
|
310
|
+
items={[
|
|
311
|
+
{
|
|
312
|
+
key: 'messages',
|
|
313
|
+
label: `Raw messages (${selectedLog.messages.length})`,
|
|
314
|
+
children: (
|
|
315
|
+
<Space direction="vertical" style={{ width: '100%' }}>
|
|
316
|
+
{selectedLog.messages.map((message: any) => (
|
|
317
|
+
<Card key={message.index} size="small" title={`${message.index + 1}. ${message.type}`}>
|
|
318
|
+
<Paragraph style={{ whiteSpace: 'pre-wrap', margin: 0, fontSize: 12 }}>
|
|
319
|
+
{message.content || JSON.stringify(message.toolCalls || message, null, 2)}
|
|
320
|
+
</Paragraph>
|
|
321
|
+
{message.toolCalls?.length > 0 && (
|
|
322
|
+
<Paragraph style={{ whiteSpace: 'pre-wrap', margin: '8px 0 0', fontSize: 12 }}>
|
|
323
|
+
{JSON.stringify(message.toolCalls, null, 2)}
|
|
324
|
+
</Paragraph>
|
|
325
|
+
)}
|
|
326
|
+
</Card>
|
|
327
|
+
))}
|
|
328
|
+
</Space>
|
|
329
|
+
),
|
|
330
|
+
},
|
|
331
|
+
]}
|
|
332
|
+
/>
|
|
333
|
+
)}
|
|
334
|
+
|
|
186
335
|
<Card
|
|
187
336
|
title="Result"
|
|
188
337
|
size="small"
|
|
@@ -210,6 +359,7 @@ export const TracingTab: React.FC = () => {
|
|
|
210
359
|
</Card>
|
|
211
360
|
)}
|
|
212
361
|
</>
|
|
362
|
+
</Spin>
|
|
213
363
|
)}
|
|
214
364
|
</Drawer>
|
|
215
365
|
</div>
|
|
@@ -39,6 +39,16 @@ export default defineCollection({
|
|
|
39
39
|
defaultValue: 120000,
|
|
40
40
|
comment: 'Timeout in ms for sub-agent execution',
|
|
41
41
|
},
|
|
42
|
+
{
|
|
43
|
+
name: 'llmService',
|
|
44
|
+
type: 'string',
|
|
45
|
+
comment: 'Optional overridden LLM provider for the sub-agent',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'model',
|
|
49
|
+
type: 'string',
|
|
50
|
+
comment: 'Optional overridden model for the sub-agent',
|
|
51
|
+
},
|
|
42
52
|
],
|
|
43
53
|
indexes: [
|
|
44
54
|
{
|
|
@@ -31,7 +31,12 @@ export default defineCollection({
|
|
|
31
31
|
{
|
|
32
32
|
name: 'toolName',
|
|
33
33
|
type: 'string',
|
|
34
|
-
comment: 'The tool name used for delegation (e.g.,
|
|
34
|
+
comment: 'The tool name used for delegation (e.g., delegate_pm_to_sql_expert)',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'context',
|
|
38
|
+
type: 'text',
|
|
39
|
+
comment: 'Optional context sent with the delegated task',
|
|
35
40
|
},
|
|
36
41
|
{
|
|
37
42
|
name: 'task',
|
|
@@ -46,7 +51,7 @@ export default defineCollection({
|
|
|
46
51
|
{
|
|
47
52
|
name: 'status',
|
|
48
53
|
type: 'string',
|
|
49
|
-
comment: 'success or error',
|
|
54
|
+
comment: 'running, success, or error',
|
|
50
55
|
},
|
|
51
56
|
{
|
|
52
57
|
name: 'depth',
|
|
@@ -64,6 +69,18 @@ export default defineCollection({
|
|
|
64
69
|
type: 'text',
|
|
65
70
|
comment: 'Error message if status is error',
|
|
66
71
|
},
|
|
72
|
+
{
|
|
73
|
+
name: 'trace',
|
|
74
|
+
type: 'json',
|
|
75
|
+
defaultValue: [],
|
|
76
|
+
comment: 'Structured timeline of sub-agent execution and tool calls',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'messages',
|
|
80
|
+
type: 'json',
|
|
81
|
+
defaultValue: [],
|
|
82
|
+
comment: 'Serialized LangChain messages from the sub-agent run',
|
|
83
|
+
},
|
|
67
84
|
{
|
|
68
85
|
name: 'userId',
|
|
69
86
|
type: 'bigInt',
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Migration } from '@nocobase/server';
|
|
2
|
+
|
|
3
|
+
export default class AddTracingDetailFieldsMigration extends Migration {
|
|
4
|
+
on = 'afterLoad';
|
|
5
|
+
appVersion = '<=2.x';
|
|
6
|
+
|
|
7
|
+
async up() {
|
|
8
|
+
const queryInterface = this.db.sequelize.getQueryInterface();
|
|
9
|
+
const DataTypes = this.db.sequelize.constructor['DataTypes'];
|
|
10
|
+
const tableName = `${this.db.options.tablePrefix || ''}orchestratorLogs`;
|
|
11
|
+
|
|
12
|
+
const tableExists = await queryInterface
|
|
13
|
+
.describeTable(tableName)
|
|
14
|
+
.then(() => true)
|
|
15
|
+
.catch(() => false);
|
|
16
|
+
|
|
17
|
+
if (!tableExists) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const columns = await queryInterface.describeTable(tableName);
|
|
22
|
+
const addIfMissing = async (name: string, definition: any) => {
|
|
23
|
+
if (!columns[name]) {
|
|
24
|
+
await queryInterface.addColumn(tableName, name, definition);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
await addIfMissing('context', { type: DataTypes.TEXT, allowNull: true });
|
|
29
|
+
await addIfMissing('trace', { type: DataTypes.JSON, allowNull: true, defaultValue: [] });
|
|
30
|
+
await addIfMissing('messages', { type: DataTypes.JSON, allowNull: true, defaultValue: [] });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async down() {
|
|
34
|
+
const queryInterface = this.db.sequelize.getQueryInterface();
|
|
35
|
+
const tableName = `${this.db.options.tablePrefix || ''}orchestratorLogs`;
|
|
36
|
+
|
|
37
|
+
for (const column of ['context', 'trace', 'messages']) {
|
|
38
|
+
await queryInterface.removeColumn(tableName, column).catch(() => {});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Migration } from '@nocobase/server';
|
|
2
|
+
|
|
3
|
+
export default class AddLlmFieldsToOrchestratorConfig extends Migration {
|
|
4
|
+
on = 'afterLoad';
|
|
5
|
+
appVersion = '>=0.1.0';
|
|
6
|
+
|
|
7
|
+
async up() {
|
|
8
|
+
const queryInterface = this.db.sequelize.getQueryInterface();
|
|
9
|
+
const tablePrefix = this.db.options.tablePrefix || '';
|
|
10
|
+
const tableName = `${tablePrefix}orchestratorConfig`;
|
|
11
|
+
|
|
12
|
+
const tableExists = await queryInterface.tableExists(tableName);
|
|
13
|
+
if (!tableExists) return;
|
|
14
|
+
|
|
15
|
+
const tableDesc = await queryInterface.describeTable(tableName);
|
|
16
|
+
|
|
17
|
+
if (!tableDesc['llmService']) {
|
|
18
|
+
await queryInterface.addColumn(tableName, 'llmService', {
|
|
19
|
+
type: 'VARCHAR(255)',
|
|
20
|
+
allowNull: true,
|
|
21
|
+
});
|
|
22
|
+
console.log(`[AgentOrchestrator] Added llmService column to ${tableName}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!tableDesc['model']) {
|
|
26
|
+
await queryInterface.addColumn(tableName, 'model', {
|
|
27
|
+
type: 'VARCHAR(255)',
|
|
28
|
+
allowNull: true,
|
|
29
|
+
});
|
|
30
|
+
console.log(`[AgentOrchestrator] Added model column to ${tableName}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async down() {
|
|
35
|
+
// No rollback
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -16,10 +16,11 @@ export function registerTracingResource(plugin: Plugin) {
|
|
|
16
16
|
*/
|
|
17
17
|
async list(ctx, next) {
|
|
18
18
|
const repo = ctx.db.getRepository('orchestratorLogs');
|
|
19
|
-
const { page = 1, pageSize = 50, sort = ['-createdAt'] } = ctx.action.params;
|
|
19
|
+
const { page = 1, pageSize = 50, sort = ['-createdAt'], filter = {} } = ctx.action.params;
|
|
20
20
|
|
|
21
21
|
try {
|
|
22
22
|
const [rows, count] = await repo.findAndCount({
|
|
23
|
+
filter,
|
|
23
24
|
sort,
|
|
24
25
|
offset: (Number(page) - 1) * Number(pageSize),
|
|
25
26
|
limit: Number(pageSize),
|
|
@@ -32,6 +33,7 @@ export function registerTracingResource(plugin: Plugin) {
|
|
|
32
33
|
subAgentUsername: row.subAgentUsername,
|
|
33
34
|
toolName: row.toolName,
|
|
34
35
|
task: row.task,
|
|
36
|
+
context: row.context,
|
|
35
37
|
result: row.result,
|
|
36
38
|
status: row.status,
|
|
37
39
|
depth: row.depth,
|
|
@@ -39,6 +41,8 @@ export function registerTracingResource(plugin: Plugin) {
|
|
|
39
41
|
error: row.error,
|
|
40
42
|
userId: row.userId,
|
|
41
43
|
createdAt: row.createdAt,
|
|
44
|
+
traceCount: Array.isArray(row.trace) ? row.trace.length : 0,
|
|
45
|
+
messageCount: Array.isArray(row.messages) ? row.messages.length : 0,
|
|
42
46
|
})),
|
|
43
47
|
meta: {
|
|
44
48
|
count,
|
|
@@ -72,7 +76,7 @@ export function registerTracingResource(plugin: Plugin) {
|
|
|
72
76
|
filter: { id: filterByTk },
|
|
73
77
|
});
|
|
74
78
|
|
|
75
|
-
ctx.body = { data: log };
|
|
79
|
+
ctx.body = { data: log?.toJSON?.() || log || null };
|
|
76
80
|
} catch (e) {
|
|
77
81
|
ctx.log.error('[AgentOrchestrator] Tracing get error', e);
|
|
78
82
|
ctx.body = { data: null };
|