plugin-agent-orchestrator 1.0.0
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/README.md +291 -0
- package/client.d.ts +2 -0
- package/client.js +1 -0
- package/dist/client/AIEmployeeSelect.js +29 -0
- package/dist/client/AIEmployeesContext.js +64 -0
- package/dist/client/OrchestratorSettings.js +32 -0
- package/dist/client/RulesTab.js +144 -0
- package/dist/client/TracingTab.js +88 -0
- package/dist/client/index.js +8 -0
- package/dist/client/locale/en-US.json +20 -0
- package/dist/client/locale/vi-VN.json +20 -0
- package/dist/client/plugin.js +17 -0
- package/dist/server/index.js +8 -0
- package/dist/server/plugin.js +44 -0
- package/dist/server/resources/tracing.js +85 -0
- package/dist/server/tools/delegate-task.js +317 -0
- package/package.json +43 -0
- package/server.d.ts +2 -0
- package/server.js +1 -0
- package/src/client/AIEmployeeSelect.tsx +49 -0
- package/src/client/AIEmployeesContext.tsx +58 -0
- package/src/client/OrchestratorSettings.tsx +46 -0
- package/src/client/RulesTab.tsx +272 -0
- package/src/client/TracingTab.tsx +227 -0
- package/src/client/index.tsx +1 -0
- package/src/client/plugin.tsx +15 -0
- package/src/index.ts +2 -0
- package/src/locale/en-US.json +20 -0
- package/src/locale/vi-VN.json +20 -0
- package/src/server/collections/orchestrator-config.ts +49 -0
- package/src/server/collections/orchestrator-logs.ts +99 -0
- package/src/server/index.ts +1 -0
- package/src/server/plugin.ts +46 -0
- package/src/server/resources/tracing.ts +91 -0
- package/src/server/tools/delegate-task.ts +388 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
|
2
|
+
import { useRequest } from '@nocobase/client';
|
|
3
|
+
|
|
4
|
+
interface AIEmployeeInfo {
|
|
5
|
+
username: string;
|
|
6
|
+
nickname: string;
|
|
7
|
+
about?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface AIEmployeesContextType {
|
|
11
|
+
employees: AIEmployeeInfo[];
|
|
12
|
+
employeeMap: Map<string, string>;
|
|
13
|
+
loading: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const AIEmployeesContext = createContext<AIEmployeesContextType>({
|
|
17
|
+
employees: [],
|
|
18
|
+
employeeMap: new Map(),
|
|
19
|
+
loading: false,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* P3 FIX: Shared context provider that fetches aiEmployees once
|
|
24
|
+
* and shares the data across RulesTab, TracingTab, and AIEmployeeSelect.
|
|
25
|
+
*/
|
|
26
|
+
export const AIEmployeesProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
27
|
+
const { data, loading } = useRequest({
|
|
28
|
+
url: 'aiEmployees:list',
|
|
29
|
+
params: { pageSize: 200 },
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const value = React.useMemo(() => {
|
|
33
|
+
const rawEmployees = (data as any)?.data || [];
|
|
34
|
+
const employees: AIEmployeeInfo[] = rawEmployees.map((emp: any) => ({
|
|
35
|
+
username: emp.username,
|
|
36
|
+
nickname: emp.nickname || emp.username,
|
|
37
|
+
about: emp.about?.substring(0, 80),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
const employeeMap = new Map<string, string>();
|
|
41
|
+
for (const emp of employees) {
|
|
42
|
+
employeeMap.set(emp.username, emp.nickname);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { employees, employeeMap, loading };
|
|
46
|
+
}, [data, loading]);
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<AIEmployeesContext.Provider value={value}>
|
|
50
|
+
{children}
|
|
51
|
+
</AIEmployeesContext.Provider>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Hook to access shared AI employees data.
|
|
57
|
+
*/
|
|
58
|
+
export const useAIEmployees = () => useContext(AIEmployeesContext);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Tabs } from 'antd';
|
|
3
|
+
import { ApartmentOutlined, MonitorOutlined } from '@ant-design/icons';
|
|
4
|
+
import { RulesTab } from './RulesTab';
|
|
5
|
+
import { TracingTab } from './TracingTab';
|
|
6
|
+
import { AIEmployeesProvider } from './AIEmployeesContext';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Main settings page for the Agent Orchestrator plugin.
|
|
10
|
+
* Contains two tabs:
|
|
11
|
+
* - Rules: Configure which AI Employees can delegate tasks to others
|
|
12
|
+
* - Tracing: View and debug delegation execution logs
|
|
13
|
+
*
|
|
14
|
+
* P3 FIX: Wraps with AIEmployeesProvider so both tabs share one API call.
|
|
15
|
+
*/
|
|
16
|
+
export const OrchestratorSettings: React.FC = () => {
|
|
17
|
+
return (
|
|
18
|
+
<AIEmployeesProvider>
|
|
19
|
+
<div style={{ padding: '0 24px 24px' }}>
|
|
20
|
+
<Tabs
|
|
21
|
+
defaultActiveKey="rules"
|
|
22
|
+
items={[
|
|
23
|
+
{
|
|
24
|
+
key: 'rules',
|
|
25
|
+
label: (
|
|
26
|
+
<span>
|
|
27
|
+
<ApartmentOutlined /> Orchestration Rules
|
|
28
|
+
</span>
|
|
29
|
+
),
|
|
30
|
+
children: <RulesTab />,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: 'tracing',
|
|
34
|
+
label: (
|
|
35
|
+
<span>
|
|
36
|
+
<MonitorOutlined /> Swarm Tracing
|
|
37
|
+
</span>
|
|
38
|
+
),
|
|
39
|
+
children: <TracingTab />,
|
|
40
|
+
},
|
|
41
|
+
]}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
</AIEmployeesProvider>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Table,
|
|
4
|
+
Button,
|
|
5
|
+
Drawer,
|
|
6
|
+
Form,
|
|
7
|
+
InputNumber,
|
|
8
|
+
Switch,
|
|
9
|
+
Space,
|
|
10
|
+
Popconfirm,
|
|
11
|
+
Card,
|
|
12
|
+
message,
|
|
13
|
+
Tag,
|
|
14
|
+
Typography,
|
|
15
|
+
Alert,
|
|
16
|
+
} from 'antd';
|
|
17
|
+
import { PlusOutlined, EditOutlined, DeleteOutlined, SwapRightOutlined } from '@ant-design/icons';
|
|
18
|
+
import { useAPIClient, useRequest } from '@nocobase/client';
|
|
19
|
+
import { AIEmployeeSelect } from './AIEmployeeSelect';
|
|
20
|
+
import { useAIEmployees } from './AIEmployeesContext';
|
|
21
|
+
|
|
22
|
+
const { Text } = Typography;
|
|
23
|
+
|
|
24
|
+
export const RulesTab: React.FC = () => {
|
|
25
|
+
const api = useAPIClient();
|
|
26
|
+
const [visible, setVisible] = useState(false);
|
|
27
|
+
const [editingRecord, setEditingRecord] = useState<any>(null);
|
|
28
|
+
const [form] = Form.useForm();
|
|
29
|
+
|
|
30
|
+
const { data, loading, refresh } = useRequest({
|
|
31
|
+
url: 'orchestratorConfig:list',
|
|
32
|
+
params: {
|
|
33
|
+
sort: ['-createdAt'],
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// P3 FIX: Use shared context instead of duplicate API call
|
|
38
|
+
const { employeeMap } = useAIEmployees();
|
|
39
|
+
|
|
40
|
+
const handleOpen = (record?: any) => {
|
|
41
|
+
setEditingRecord(record);
|
|
42
|
+
if (record) {
|
|
43
|
+
form.setFieldsValue(record);
|
|
44
|
+
} else {
|
|
45
|
+
form.resetFields();
|
|
46
|
+
form.setFieldsValue({ enabled: true, maxDepth: 1, timeout: 120000 });
|
|
47
|
+
}
|
|
48
|
+
setVisible(true);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const handleClose = () => {
|
|
52
|
+
setVisible(false);
|
|
53
|
+
setEditingRecord(null);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const handleSave = async (values: any) => {
|
|
57
|
+
// Validate: leader !== subAgent
|
|
58
|
+
if (values.leaderUsername === values.subAgentUsername) {
|
|
59
|
+
message.error('Leader and Sub-Agent cannot be the same employee.');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
if (editingRecord) {
|
|
65
|
+
await api.request({
|
|
66
|
+
url: 'orchestratorConfig:update',
|
|
67
|
+
method: 'put',
|
|
68
|
+
params: { filterByTk: editingRecord.id },
|
|
69
|
+
data: values,
|
|
70
|
+
});
|
|
71
|
+
message.success('Rule updated');
|
|
72
|
+
} else {
|
|
73
|
+
await api.request({
|
|
74
|
+
url: 'orchestratorConfig:create',
|
|
75
|
+
method: 'post',
|
|
76
|
+
data: values,
|
|
77
|
+
});
|
|
78
|
+
message.success('Rule created');
|
|
79
|
+
}
|
|
80
|
+
handleClose();
|
|
81
|
+
refresh();
|
|
82
|
+
} catch (e: any) {
|
|
83
|
+
const msg = e?.response?.data?.errors?.[0]?.message || e.message;
|
|
84
|
+
message.error(`Save failed: ${msg}`);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const handleDelete = async (id: string) => {
|
|
89
|
+
try {
|
|
90
|
+
await api.request({
|
|
91
|
+
url: 'orchestratorConfig:destroy',
|
|
92
|
+
method: 'delete',
|
|
93
|
+
params: { filterByTk: id },
|
|
94
|
+
});
|
|
95
|
+
message.success('Rule deleted');
|
|
96
|
+
refresh();
|
|
97
|
+
} catch (e: any) {
|
|
98
|
+
message.error(`Delete failed: ${e.message}`);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const columns = [
|
|
103
|
+
{
|
|
104
|
+
title: 'Leader (Orchestrator)',
|
|
105
|
+
dataIndex: 'leaderUsername',
|
|
106
|
+
key: 'leaderUsername',
|
|
107
|
+
render: (username: string) => (
|
|
108
|
+
<Tag color="blue">{employeeMap.get(username) || username}</Tag>
|
|
109
|
+
),
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
title: '',
|
|
113
|
+
key: 'arrow',
|
|
114
|
+
width: 50,
|
|
115
|
+
render: () => <SwapRightOutlined style={{ color: '#999', fontSize: 18 }} />,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
title: 'Sub-Agent',
|
|
119
|
+
dataIndex: 'subAgentUsername',
|
|
120
|
+
key: 'subAgentUsername',
|
|
121
|
+
render: (username: string) => (
|
|
122
|
+
<Tag color="green">{employeeMap.get(username) || username}</Tag>
|
|
123
|
+
),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
title: 'Max Depth',
|
|
127
|
+
dataIndex: 'maxDepth',
|
|
128
|
+
key: 'maxDepth',
|
|
129
|
+
width: 100,
|
|
130
|
+
render: (v: number) => v ?? 1,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
title: 'Timeout',
|
|
134
|
+
dataIndex: 'timeout',
|
|
135
|
+
key: 'timeout',
|
|
136
|
+
width: 100,
|
|
137
|
+
render: (v: number) => `${((v ?? 120000) / 1000).toFixed(0)}s`,
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
title: 'Enabled',
|
|
141
|
+
dataIndex: 'enabled',
|
|
142
|
+
key: 'enabled',
|
|
143
|
+
width: 80,
|
|
144
|
+
render: (enabled: boolean, record: any) => (
|
|
145
|
+
<Switch
|
|
146
|
+
checked={enabled}
|
|
147
|
+
size="small"
|
|
148
|
+
onChange={async (checked) => {
|
|
149
|
+
await api.request({
|
|
150
|
+
url: 'orchestratorConfig:update',
|
|
151
|
+
method: 'put',
|
|
152
|
+
params: { filterByTk: record.id },
|
|
153
|
+
data: { enabled: checked },
|
|
154
|
+
});
|
|
155
|
+
refresh();
|
|
156
|
+
}}
|
|
157
|
+
/>
|
|
158
|
+
),
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
title: 'Actions',
|
|
162
|
+
key: 'actions',
|
|
163
|
+
width: 160,
|
|
164
|
+
render: (_: any, record: any) => (
|
|
165
|
+
<Space>
|
|
166
|
+
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => handleOpen(record)}>
|
|
167
|
+
Edit
|
|
168
|
+
</Button>
|
|
169
|
+
<Popconfirm title="Delete this rule?" onConfirm={() => handleDelete(record.id)}>
|
|
170
|
+
<Button type="link" size="small" danger icon={<DeleteOutlined />}>
|
|
171
|
+
Delete
|
|
172
|
+
</Button>
|
|
173
|
+
</Popconfirm>
|
|
174
|
+
</Space>
|
|
175
|
+
),
|
|
176
|
+
},
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
const leaderUsername = Form.useWatch('leaderUsername', form);
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<div>
|
|
183
|
+
<Alert
|
|
184
|
+
type="info"
|
|
185
|
+
showIcon
|
|
186
|
+
style={{ marginBottom: 16 }}
|
|
187
|
+
message="Orchestration Rules"
|
|
188
|
+
description={
|
|
189
|
+
<Text type="secondary">
|
|
190
|
+
Configure which AI Employees can act as Leaders (Orchestrators) and which ones they can delegate tasks to.
|
|
191
|
+
Each rule creates a callable tool for the Leader to invoke the Sub-Agent.
|
|
192
|
+
</Text>
|
|
193
|
+
}
|
|
194
|
+
/>
|
|
195
|
+
|
|
196
|
+
<Card bordered={false}>
|
|
197
|
+
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'flex-end' }}>
|
|
198
|
+
<Button type="primary" icon={<PlusOutlined />} onClick={() => handleOpen()}>
|
|
199
|
+
New Rule
|
|
200
|
+
</Button>
|
|
201
|
+
</div>
|
|
202
|
+
<Table
|
|
203
|
+
rowKey="id"
|
|
204
|
+
loading={loading}
|
|
205
|
+
dataSource={(data as any)?.data || []}
|
|
206
|
+
columns={columns}
|
|
207
|
+
pagination={{ hideOnSinglePage: true, pageSize: 20 }}
|
|
208
|
+
size="middle"
|
|
209
|
+
/>
|
|
210
|
+
</Card>
|
|
211
|
+
|
|
212
|
+
<Drawer
|
|
213
|
+
title={editingRecord ? 'Edit Orchestration Rule' : 'New Orchestration Rule'}
|
|
214
|
+
width={480}
|
|
215
|
+
onClose={handleClose}
|
|
216
|
+
open={visible}
|
|
217
|
+
styles={{ body: { paddingBottom: 80 } }}
|
|
218
|
+
extra={
|
|
219
|
+
<Space>
|
|
220
|
+
<Button onClick={handleClose}>Cancel</Button>
|
|
221
|
+
<Button onClick={() => form.submit()} type="primary">
|
|
222
|
+
Save
|
|
223
|
+
</Button>
|
|
224
|
+
</Space>
|
|
225
|
+
}
|
|
226
|
+
>
|
|
227
|
+
<Form form={form} layout="vertical" onFinish={handleSave}>
|
|
228
|
+
<Form.Item
|
|
229
|
+
name="leaderUsername"
|
|
230
|
+
label="Leader (Orchestrator)"
|
|
231
|
+
rules={[{ required: true, message: 'Please select a Leader' }]}
|
|
232
|
+
tooltip="The AI Employee that will be able to delegate tasks to the Sub-Agent"
|
|
233
|
+
>
|
|
234
|
+
<AIEmployeeSelect placeholder="Select Leader AI Employee..." />
|
|
235
|
+
</Form.Item>
|
|
236
|
+
|
|
237
|
+
<Form.Item
|
|
238
|
+
name="subAgentUsername"
|
|
239
|
+
label="Sub-Agent"
|
|
240
|
+
rules={[{ required: true, message: 'Please select a Sub-Agent' }]}
|
|
241
|
+
tooltip="The AI Employee that will receive delegated tasks"
|
|
242
|
+
>
|
|
243
|
+
<AIEmployeeSelect
|
|
244
|
+
placeholder="Select Sub-Agent AI Employee..."
|
|
245
|
+
exclude={leaderUsername}
|
|
246
|
+
/>
|
|
247
|
+
</Form.Item>
|
|
248
|
+
|
|
249
|
+
<Form.Item
|
|
250
|
+
name="maxDepth"
|
|
251
|
+
label="Max Delegation Depth"
|
|
252
|
+
tooltip="How many layers of delegation are allowed (1 = leader calls sub-agent, sub-agent cannot delegate further)"
|
|
253
|
+
>
|
|
254
|
+
<InputNumber min={1} max={3} style={{ width: '100%' }} />
|
|
255
|
+
</Form.Item>
|
|
256
|
+
|
|
257
|
+
<Form.Item
|
|
258
|
+
name="timeout"
|
|
259
|
+
label="Timeout (ms)"
|
|
260
|
+
tooltip="Maximum time in milliseconds for the sub-agent to complete its task"
|
|
261
|
+
>
|
|
262
|
+
<InputNumber min={10000} max={600000} step={10000} style={{ width: '100%' }} />
|
|
263
|
+
</Form.Item>
|
|
264
|
+
|
|
265
|
+
<Form.Item name="enabled" label="Enabled" valuePropName="checked">
|
|
266
|
+
<Switch />
|
|
267
|
+
</Form.Item>
|
|
268
|
+
</Form>
|
|
269
|
+
</Drawer>
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
272
|
+
};
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Table,
|
|
4
|
+
Card,
|
|
5
|
+
Tag,
|
|
6
|
+
Typography,
|
|
7
|
+
Drawer,
|
|
8
|
+
Descriptions,
|
|
9
|
+
Alert,
|
|
10
|
+
Button,
|
|
11
|
+
Empty,
|
|
12
|
+
} from 'antd';
|
|
13
|
+
import {
|
|
14
|
+
EyeOutlined,
|
|
15
|
+
CheckCircleOutlined,
|
|
16
|
+
CloseCircleOutlined,
|
|
17
|
+
ClockCircleOutlined,
|
|
18
|
+
} from '@ant-design/icons';
|
|
19
|
+
import { useRequest } from '@nocobase/client';
|
|
20
|
+
import { useAIEmployees } from './AIEmployeesContext';
|
|
21
|
+
|
|
22
|
+
const { Text, Paragraph } = Typography;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Phase 5: Swarm Tracing page.
|
|
26
|
+
* Displays delegation execution logs from the orchestratorLogs collection.
|
|
27
|
+
*/
|
|
28
|
+
export const TracingTab: React.FC = () => {
|
|
29
|
+
const [selectedLog, setSelectedLog] = useState<any>(null);
|
|
30
|
+
|
|
31
|
+
// Fetch delegation logs
|
|
32
|
+
const { data, loading, refresh } = useRequest({
|
|
33
|
+
url: 'orchestratorTracing:list',
|
|
34
|
+
params: {
|
|
35
|
+
sort: ['-createdAt'],
|
|
36
|
+
pageSize: 50,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// P3 FIX: Use shared context instead of duplicate API call
|
|
41
|
+
const { employeeMap } = useAIEmployees();
|
|
42
|
+
|
|
43
|
+
const columns = [
|
|
44
|
+
{
|
|
45
|
+
title: 'Time',
|
|
46
|
+
dataIndex: 'createdAt',
|
|
47
|
+
key: 'createdAt',
|
|
48
|
+
width: 170,
|
|
49
|
+
render: (v: string) => v ? new Date(v).toLocaleString() : '-',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: 'Sub-Agent',
|
|
53
|
+
dataIndex: 'subAgentUsername',
|
|
54
|
+
key: 'subAgentUsername',
|
|
55
|
+
render: (username: string) => (
|
|
56
|
+
<Tag color="green">{employeeMap.get(username) || username}</Tag>
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
title: 'Task',
|
|
61
|
+
dataIndex: 'task',
|
|
62
|
+
key: 'task',
|
|
63
|
+
render: (task: string) => (
|
|
64
|
+
<Text ellipsis style={{ maxWidth: 280 }}>
|
|
65
|
+
{task?.substring(0, 100) || '-'}
|
|
66
|
+
</Text>
|
|
67
|
+
),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
title: 'Status',
|
|
71
|
+
dataIndex: 'status',
|
|
72
|
+
key: 'status',
|
|
73
|
+
width: 90,
|
|
74
|
+
render: (status: string) => (
|
|
75
|
+
<Tag
|
|
76
|
+
icon={status === 'success' ? <CheckCircleOutlined /> : <CloseCircleOutlined />}
|
|
77
|
+
color={status === 'success' ? 'success' : 'error'}
|
|
78
|
+
>
|
|
79
|
+
{status}
|
|
80
|
+
</Tag>
|
|
81
|
+
),
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
title: 'Duration',
|
|
85
|
+
dataIndex: 'durationMs',
|
|
86
|
+
key: 'durationMs',
|
|
87
|
+
width: 90,
|
|
88
|
+
render: (ms: number) => {
|
|
89
|
+
if (!ms) return '-';
|
|
90
|
+
return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`;
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
title: 'Depth',
|
|
95
|
+
dataIndex: 'depth',
|
|
96
|
+
key: 'depth',
|
|
97
|
+
width: 60,
|
|
98
|
+
render: (v: number) => v ?? 0,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
title: '',
|
|
102
|
+
key: 'actions',
|
|
103
|
+
width: 80,
|
|
104
|
+
render: (_: any, record: any) => (
|
|
105
|
+
<Button
|
|
106
|
+
type="link"
|
|
107
|
+
size="small"
|
|
108
|
+
icon={<EyeOutlined />}
|
|
109
|
+
onClick={() => setSelectedLog(record)}
|
|
110
|
+
>
|
|
111
|
+
Detail
|
|
112
|
+
</Button>
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div>
|
|
119
|
+
<Alert
|
|
120
|
+
type="info"
|
|
121
|
+
showIcon
|
|
122
|
+
style={{ marginBottom: 16 }}
|
|
123
|
+
message="Swarm Tracing"
|
|
124
|
+
description={
|
|
125
|
+
<Text type="secondary">
|
|
126
|
+
View delegation execution logs. Each row represents one sub-agent invocation
|
|
127
|
+
triggered by a Leader's tool call.
|
|
128
|
+
</Text>
|
|
129
|
+
}
|
|
130
|
+
/>
|
|
131
|
+
|
|
132
|
+
<Card bordered={false}>
|
|
133
|
+
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'flex-end' }}>
|
|
134
|
+
<Button onClick={refresh}>Refresh</Button>
|
|
135
|
+
</div>
|
|
136
|
+
<Table
|
|
137
|
+
rowKey="id"
|
|
138
|
+
loading={loading}
|
|
139
|
+
dataSource={(data as any)?.data || []}
|
|
140
|
+
columns={columns}
|
|
141
|
+
pagination={{ hideOnSinglePage: true, pageSize: 20 }}
|
|
142
|
+
size="middle"
|
|
143
|
+
locale={{ emptyText: <Empty description="No delegation executions yet" /> }}
|
|
144
|
+
/>
|
|
145
|
+
</Card>
|
|
146
|
+
|
|
147
|
+
{/* Detail drawer */}
|
|
148
|
+
<Drawer
|
|
149
|
+
title="Delegation Detail"
|
|
150
|
+
width={640}
|
|
151
|
+
onClose={() => setSelectedLog(null)}
|
|
152
|
+
open={!!selectedLog}
|
|
153
|
+
>
|
|
154
|
+
{selectedLog && (
|
|
155
|
+
<>
|
|
156
|
+
<Descriptions column={1} bordered size="small" style={{ marginBottom: 16 }}>
|
|
157
|
+
<Descriptions.Item label="Status">
|
|
158
|
+
<Tag
|
|
159
|
+
icon={selectedLog.status === 'success' ? <CheckCircleOutlined /> : <CloseCircleOutlined />}
|
|
160
|
+
color={selectedLog.status === 'success' ? 'success' : 'error'}
|
|
161
|
+
>
|
|
162
|
+
{selectedLog.status}
|
|
163
|
+
</Tag>
|
|
164
|
+
</Descriptions.Item>
|
|
165
|
+
<Descriptions.Item label="Sub-Agent">
|
|
166
|
+
<Tag color="green">
|
|
167
|
+
{employeeMap.get(selectedLog.subAgentUsername) || selectedLog.subAgentUsername}
|
|
168
|
+
</Tag>
|
|
169
|
+
</Descriptions.Item>
|
|
170
|
+
<Descriptions.Item label="Tool">
|
|
171
|
+
<Text code>{selectedLog.toolName}</Text>
|
|
172
|
+
</Descriptions.Item>
|
|
173
|
+
<Descriptions.Item label="Depth">
|
|
174
|
+
{selectedLog.depth ?? 0}
|
|
175
|
+
</Descriptions.Item>
|
|
176
|
+
<Descriptions.Item label="Duration">
|
|
177
|
+
{selectedLog.durationMs
|
|
178
|
+
? selectedLog.durationMs >= 1000
|
|
179
|
+
? `${(selectedLog.durationMs / 1000).toFixed(1)}s`
|
|
180
|
+
: `${selectedLog.durationMs}ms`
|
|
181
|
+
: '-'}
|
|
182
|
+
</Descriptions.Item>
|
|
183
|
+
<Descriptions.Item label="Time">
|
|
184
|
+
{selectedLog.createdAt ? new Date(selectedLog.createdAt).toLocaleString() : '-'}
|
|
185
|
+
</Descriptions.Item>
|
|
186
|
+
</Descriptions>
|
|
187
|
+
|
|
188
|
+
<Card title="Task" size="small" style={{ marginBottom: 16 }}>
|
|
189
|
+
<Paragraph
|
|
190
|
+
style={{ whiteSpace: 'pre-wrap', margin: 0, fontSize: 13 }}
|
|
191
|
+
>
|
|
192
|
+
{selectedLog.task || 'No task description'}
|
|
193
|
+
</Paragraph>
|
|
194
|
+
</Card>
|
|
195
|
+
|
|
196
|
+
<Card
|
|
197
|
+
title="Result"
|
|
198
|
+
size="small"
|
|
199
|
+
style={{
|
|
200
|
+
marginBottom: 16,
|
|
201
|
+
borderColor: selectedLog.status === 'success' ? '#b7eb8f' : '#ffa39e',
|
|
202
|
+
}}
|
|
203
|
+
>
|
|
204
|
+
<Paragraph
|
|
205
|
+
style={{ whiteSpace: 'pre-wrap', margin: 0, fontSize: 13 }}
|
|
206
|
+
ellipsis={{ rows: 20, expandable: true }}
|
|
207
|
+
>
|
|
208
|
+
{selectedLog.result || selectedLog.error || 'No result'}
|
|
209
|
+
</Paragraph>
|
|
210
|
+
</Card>
|
|
211
|
+
|
|
212
|
+
{selectedLog.error && (
|
|
213
|
+
<Card title="Error" size="small" style={{ borderColor: '#ffa39e' }}>
|
|
214
|
+
<Paragraph
|
|
215
|
+
type="danger"
|
|
216
|
+
style={{ whiteSpace: 'pre-wrap', margin: 0, fontSize: 13 }}
|
|
217
|
+
>
|
|
218
|
+
{selectedLog.error}
|
|
219
|
+
</Paragraph>
|
|
220
|
+
</Card>
|
|
221
|
+
)}
|
|
222
|
+
</>
|
|
223
|
+
)}
|
|
224
|
+
</Drawer>
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './plugin';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Plugin } from '@nocobase/client';
|
|
2
|
+
import { OrchestratorSettings } from './OrchestratorSettings';
|
|
3
|
+
|
|
4
|
+
export class PluginAgentOrchestratorClient extends Plugin {
|
|
5
|
+
async load() {
|
|
6
|
+
// Register under the "AI" settings group for consistency with other AI plugins
|
|
7
|
+
this.app.pluginSettingsManager.add('ai.orchestrator', {
|
|
8
|
+
title: 'Agent Orchestrator',
|
|
9
|
+
icon: 'ApartmentOutlined',
|
|
10
|
+
Component: OrchestratorSettings,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default PluginAgentOrchestratorClient;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Agent Orchestrator": "Agent Orchestrator",
|
|
3
|
+
"Orchestration Rules": "Orchestration Rules",
|
|
4
|
+
"Swarm Tracing": "Swarm Tracing",
|
|
5
|
+
"Leader (Orchestrator)": "Leader (Orchestrator)",
|
|
6
|
+
"Sub-Agent": "Sub-Agent",
|
|
7
|
+
"Max Delegation Depth": "Max Delegation Depth",
|
|
8
|
+
"Timeout (ms)": "Timeout (ms)",
|
|
9
|
+
"Enabled": "Enabled",
|
|
10
|
+
"New Rule": "New Rule",
|
|
11
|
+
"Edit Orchestration Rule": "Edit Orchestration Rule",
|
|
12
|
+
"New Orchestration Rule": "New Orchestration Rule",
|
|
13
|
+
"Rule created": "Rule created",
|
|
14
|
+
"Rule updated": "Rule updated",
|
|
15
|
+
"Rule deleted": "Rule deleted",
|
|
16
|
+
"Sub-Agent Conversation": "Sub-Agent Conversation",
|
|
17
|
+
"Task Summary": "Task Summary",
|
|
18
|
+
"Parent Session": "Parent Session",
|
|
19
|
+
"No sub-agent executions yet": "No sub-agent executions yet"
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Agent Orchestrator": "Điều phối Agent",
|
|
3
|
+
"Orchestration Rules": "Quy tắc điều phối",
|
|
4
|
+
"Swarm Tracing": "Truy vết Swarm",
|
|
5
|
+
"Leader (Orchestrator)": "Leader (Điều phối viên)",
|
|
6
|
+
"Sub-Agent": "Agent con",
|
|
7
|
+
"Max Delegation Depth": "Độ sâu ủy quyền tối đa",
|
|
8
|
+
"Timeout (ms)": "Thời gian chờ (ms)",
|
|
9
|
+
"Enabled": "Bật",
|
|
10
|
+
"New Rule": "Quy tắc mới",
|
|
11
|
+
"Edit Orchestration Rule": "Sửa quy tắc điều phối",
|
|
12
|
+
"New Orchestration Rule": "Quy tắc điều phối mới",
|
|
13
|
+
"Rule created": "Đã tạo quy tắc",
|
|
14
|
+
"Rule updated": "Đã cập nhật quy tắc",
|
|
15
|
+
"Rule deleted": "Đã xóa quy tắc",
|
|
16
|
+
"Sub-Agent Conversation": "Hội thoại Agent con",
|
|
17
|
+
"Task Summary": "Tóm tắt nhiệm vụ",
|
|
18
|
+
"Parent Session": "Phiên gốc",
|
|
19
|
+
"No sub-agent executions yet": "Chưa có lượt thực thi agent con nào"
|
|
20
|
+
}
|