plugin-agent-orchestrator 1.0.16 → 1.0.18
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/AgentRunsTab.d.ts +2 -0
- package/dist/client/HarnessProfilesTab.d.ts +2 -0
- package/dist/client/index.js +1 -1
- package/dist/client/skill-hub/components/LoopSettings.d.ts +2 -0
- package/dist/client/skill-hub/index.d.ts +2 -1
- package/dist/client/skill-hub/tools/InteractionSchemasProvider.d.ts +1 -14
- package/dist/client/skill-hub/tools/loopTemplates.d.ts +22 -0
- package/dist/client/skill-hub/tools/registerSkillLoopCards.d.ts +1 -0
- package/dist/client/tools/PlanApprovalCard.d.ts +3 -0
- package/dist/client/tools/registerOrchestratorCards.d.ts +1 -0
- package/dist/externalVersion.js +6 -6
- package/dist/server/collections/agent-harness-profiles.d.ts +2 -0
- package/dist/server/collections/agent-harness-profiles.js +89 -0
- package/dist/server/collections/agent-loop-events.d.ts +2 -0
- package/dist/server/collections/agent-loop-events.js +101 -0
- package/dist/server/collections/agent-loop-runs.d.ts +2 -0
- package/dist/server/collections/agent-loop-runs.js +188 -0
- package/dist/server/collections/agent-loop-steps.d.ts +2 -0
- package/dist/server/collections/agent-loop-steps.js +174 -0
- package/dist/server/collections/orchestrator-config.js +7 -0
- package/dist/server/collections/skill-executions.js +12 -0
- package/dist/server/collections/skill-loop-configs.d.ts +3 -0
- package/dist/server/collections/skill-loop-configs.js +94 -0
- package/dist/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.d.ts +7 -0
- package/dist/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.js +55 -0
- package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.d.ts +12 -0
- package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.js +162 -0
- package/dist/server/plugin.d.ts +2 -0
- package/dist/server/plugin.js +13 -0
- package/dist/server/resources/agent-loop.d.ts +3 -0
- package/dist/server/resources/agent-loop.js +205 -0
- package/dist/server/services/AgentHarness.d.ts +42 -0
- package/dist/server/services/AgentHarness.js +565 -0
- package/dist/server/services/AgentLoopController.d.ts +205 -0
- package/dist/server/services/AgentLoopController.js +940 -0
- package/dist/server/services/AgentLoopRepository.d.ts +20 -0
- package/dist/server/services/AgentLoopRepository.js +210 -0
- package/dist/server/services/AgentLoopService.d.ts +149 -0
- package/dist/server/services/AgentLoopService.js +133 -0
- package/dist/server/services/AgentPlanValidator.d.ts +4 -0
- package/dist/server/services/AgentPlanValidator.js +99 -0
- package/dist/server/services/AgentPlannerService.d.ts +8 -0
- package/dist/server/services/AgentPlannerService.js +119 -0
- package/dist/server/services/AgentRegistryService.d.ts +13 -0
- package/dist/server/services/AgentRegistryService.js +178 -0
- package/dist/server/services/ExecutionSpanService.d.ts +2 -0
- package/dist/server/skill-hub/plugin.d.ts +3 -0
- package/dist/server/skill-hub/plugin.js +137 -54
- package/dist/server/tools/agent-loop.d.ts +235 -0
- package/dist/server/tools/agent-loop.js +406 -0
- package/dist/server/tools/delegate-task.js +37 -350
- package/dist/server/tools/orchestrator-plan.d.ts +205 -0
- package/dist/server/tools/orchestrator-plan.js +291 -0
- package/dist/server/tools/skill-execute.js +2 -0
- package/package.json +2 -2
- package/src/client/AgentRunsTab.tsx +764 -0
- package/src/client/HarnessProfilesTab.tsx +247 -0
- package/src/client/OrchestratorSettings.tsx +40 -2
- package/src/client/RulesTab.tsx +103 -6
- package/src/client/plugin.tsx +27 -54
- package/src/client/skill-hub/components/LoopSettings.tsx +331 -0
- package/src/client/skill-hub/index.tsx +51 -75
- package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +56 -16
- package/src/client/skill-hub/tools/SkillHubCard.tsx +35 -4
- package/src/client/skill-hub/tools/loopTemplates.ts +52 -0
- package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -0
- package/src/client/tools/PlanApprovalCard.tsx +175 -0
- package/src/client/tools/registerOrchestratorCards.ts +7 -0
- package/src/server/collections/agent-harness-profiles.ts +59 -0
- package/src/server/collections/agent-loop-events.ts +71 -0
- package/src/server/collections/agent-loop-runs.ts +158 -0
- package/src/server/collections/agent-loop-steps.ts +144 -0
- package/src/server/collections/orchestrator-config.ts +7 -0
- package/src/server/collections/skill-executions.ts +63 -51
- package/src/server/collections/skill-loop-configs.ts +65 -0
- package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -0
- package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -0
- package/src/server/plugin.ts +15 -0
- package/src/server/resources/agent-loop.ts +183 -0
- package/src/server/services/AgentHarness.ts +663 -0
- package/src/server/services/AgentLoopController.ts +1128 -0
- package/src/server/services/AgentLoopRepository.ts +194 -0
- package/src/server/services/AgentLoopService.ts +161 -0
- package/src/server/services/AgentPlanValidator.ts +73 -0
- package/src/server/services/AgentPlannerService.ts +93 -0
- package/src/server/services/AgentRegistryService.ts +169 -0
- package/src/server/services/ExecutionSpanService.ts +2 -0
- package/src/server/skill-hub/plugin.ts +881 -771
- package/src/server/tools/agent-loop.ts +399 -0
- package/src/server/tools/delegate-task.ts +48 -463
- package/src/server/tools/orchestrator-plan.ts +279 -0
- package/src/server/tools/skill-execute.ts +68 -64
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button, Card, Drawer, Form, Input, Popconfirm, Space, Switch, Table, Tag, Typography, message } from 'antd';
|
|
3
|
+
import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
|
|
4
|
+
import { useAPIClient, useRequest } from '@nocobase/client';
|
|
5
|
+
|
|
6
|
+
const { Text } = Typography;
|
|
7
|
+
|
|
8
|
+
const parseSettings = (value: string) => {
|
|
9
|
+
const text = String(value || '').trim();
|
|
10
|
+
if (!text) return {};
|
|
11
|
+
return JSON.parse(text);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const HarnessProfilesTab: React.FC = () => {
|
|
15
|
+
const api = useAPIClient();
|
|
16
|
+
const [open, setOpen] = React.useState(false);
|
|
17
|
+
const [editingRecord, setEditingRecord] = React.useState<any>(null);
|
|
18
|
+
const [form] = Form.useForm();
|
|
19
|
+
|
|
20
|
+
const { data, loading, refresh } = useRequest({
|
|
21
|
+
url: 'agentHarnessProfiles:list',
|
|
22
|
+
params: {
|
|
23
|
+
sort: ['tag'],
|
|
24
|
+
pageSize: 100,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const rows = React.useMemo(() => {
|
|
29
|
+
const raw = (data as any)?.data;
|
|
30
|
+
return Array.isArray(raw) ? raw : [];
|
|
31
|
+
}, [data]);
|
|
32
|
+
|
|
33
|
+
const openDrawer = (record?: any) => {
|
|
34
|
+
setEditingRecord(record || null);
|
|
35
|
+
form.resetFields();
|
|
36
|
+
form.setFieldsValue(
|
|
37
|
+
record
|
|
38
|
+
? {
|
|
39
|
+
...record,
|
|
40
|
+
settingsText: JSON.stringify(record.settings || {}, null, 2),
|
|
41
|
+
}
|
|
42
|
+
: {
|
|
43
|
+
tag: '',
|
|
44
|
+
title: '',
|
|
45
|
+
description: '',
|
|
46
|
+
enabled: true,
|
|
47
|
+
settingsText: JSON.stringify(
|
|
48
|
+
{
|
|
49
|
+
requirePlanApproval: true,
|
|
50
|
+
allowSubAgents: true,
|
|
51
|
+
allowToolCalls: true,
|
|
52
|
+
maxParallelSubAgents: 3,
|
|
53
|
+
maxControllerSteps: 100,
|
|
54
|
+
},
|
|
55
|
+
null,
|
|
56
|
+
2,
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
setOpen(true);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const closeDrawer = () => {
|
|
64
|
+
setOpen(false);
|
|
65
|
+
setEditingRecord(null);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const saveProfile = async (values: any) => {
|
|
69
|
+
let settings: any;
|
|
70
|
+
try {
|
|
71
|
+
settings = parseSettings(values.settingsText);
|
|
72
|
+
} catch (error: any) {
|
|
73
|
+
message.error(`Settings JSON is invalid: ${error?.message || error}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const payload = {
|
|
78
|
+
tag: String(values.tag || '').trim(),
|
|
79
|
+
title: values.title,
|
|
80
|
+
description: values.description,
|
|
81
|
+
enabled: values.enabled !== false,
|
|
82
|
+
settings,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
if (editingRecord) {
|
|
87
|
+
await api.request({
|
|
88
|
+
url: 'agentHarnessProfiles:update',
|
|
89
|
+
method: 'put',
|
|
90
|
+
params: { filterByTk: editingRecord.id },
|
|
91
|
+
data: payload,
|
|
92
|
+
});
|
|
93
|
+
message.success('Harness profile updated');
|
|
94
|
+
} else {
|
|
95
|
+
await api.request({
|
|
96
|
+
url: 'agentHarnessProfiles:create',
|
|
97
|
+
method: 'post',
|
|
98
|
+
data: payload,
|
|
99
|
+
});
|
|
100
|
+
message.success('Harness profile created');
|
|
101
|
+
}
|
|
102
|
+
closeDrawer();
|
|
103
|
+
refresh();
|
|
104
|
+
} catch (error: any) {
|
|
105
|
+
const msg = error?.response?.data?.errors?.[0]?.message || error?.message || 'unknown error';
|
|
106
|
+
message.error(`Save failed: ${msg}`);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const deleteProfile = async (id: string | number) => {
|
|
111
|
+
try {
|
|
112
|
+
await api.request({
|
|
113
|
+
url: 'agentHarnessProfiles:destroy',
|
|
114
|
+
method: 'delete',
|
|
115
|
+
params: { filterByTk: id },
|
|
116
|
+
});
|
|
117
|
+
message.success('Harness profile deleted');
|
|
118
|
+
refresh();
|
|
119
|
+
} catch (error: any) {
|
|
120
|
+
message.error(`Delete failed: ${error?.message || 'unknown error'}`);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const columns = [
|
|
125
|
+
{
|
|
126
|
+
title: 'Tag',
|
|
127
|
+
dataIndex: 'tag',
|
|
128
|
+
key: 'tag',
|
|
129
|
+
width: 140,
|
|
130
|
+
render: (tag: string) => <Tag color="blue">{tag}</Tag>,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
title: 'Title',
|
|
134
|
+
dataIndex: 'title',
|
|
135
|
+
key: 'title',
|
|
136
|
+
render: (title: string, record: any) => title || record.tag,
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
title: 'Enabled',
|
|
140
|
+
dataIndex: 'enabled',
|
|
141
|
+
key: 'enabled',
|
|
142
|
+
width: 90,
|
|
143
|
+
render: (enabled: boolean, record: any) => (
|
|
144
|
+
<Switch
|
|
145
|
+
size="small"
|
|
146
|
+
checked={enabled !== false}
|
|
147
|
+
onChange={async (checked) => {
|
|
148
|
+
await api.request({
|
|
149
|
+
url: 'agentHarnessProfiles:update',
|
|
150
|
+
method: 'put',
|
|
151
|
+
params: { filterByTk: record.id },
|
|
152
|
+
data: { enabled: checked },
|
|
153
|
+
});
|
|
154
|
+
refresh();
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
157
|
+
),
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
title: 'Settings',
|
|
161
|
+
key: 'settings',
|
|
162
|
+
render: (_: any, record: any) => (
|
|
163
|
+
<Space size={4} wrap>
|
|
164
|
+
{Object.entries(record.settings || {})
|
|
165
|
+
.slice(0, 5)
|
|
166
|
+
.map(([key, value]) => (
|
|
167
|
+
<Tag key={key}>
|
|
168
|
+
{key}: {String(value)}
|
|
169
|
+
</Tag>
|
|
170
|
+
))}
|
|
171
|
+
</Space>
|
|
172
|
+
),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
title: 'Actions',
|
|
176
|
+
key: 'actions',
|
|
177
|
+
width: 150,
|
|
178
|
+
render: (_: any, record: any) => (
|
|
179
|
+
<Space>
|
|
180
|
+
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => openDrawer(record)}>
|
|
181
|
+
Edit
|
|
182
|
+
</Button>
|
|
183
|
+
<Popconfirm title="Delete this profile?" onConfirm={() => deleteProfile(record.id)}>
|
|
184
|
+
<Button type="link" size="small" danger icon={<DeleteOutlined />}>
|
|
185
|
+
Delete
|
|
186
|
+
</Button>
|
|
187
|
+
</Popconfirm>
|
|
188
|
+
</Space>
|
|
189
|
+
),
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<div>
|
|
195
|
+
<Card bordered={false}>
|
|
196
|
+
<Space direction="vertical" size={16} style={{ width: '100%' }}>
|
|
197
|
+
<div style={{ display: 'flex', justifyContent: 'space-between', gap: 16 }}>
|
|
198
|
+
<Text type="secondary">
|
|
199
|
+
Harness profiles are selected by orchestration rules through the harnessTag field.
|
|
200
|
+
</Text>
|
|
201
|
+
<Button type="primary" icon={<PlusOutlined />} onClick={() => openDrawer()}>
|
|
202
|
+
New Profile
|
|
203
|
+
</Button>
|
|
204
|
+
</div>
|
|
205
|
+
<Table rowKey="id" loading={loading} dataSource={rows} columns={columns} pagination={false} />
|
|
206
|
+
</Space>
|
|
207
|
+
</Card>
|
|
208
|
+
|
|
209
|
+
<Drawer
|
|
210
|
+
title={editingRecord ? 'Edit Harness Profile' : 'New Harness Profile'}
|
|
211
|
+
width={560}
|
|
212
|
+
open={open}
|
|
213
|
+
onClose={closeDrawer}
|
|
214
|
+
extra={
|
|
215
|
+
<Space>
|
|
216
|
+
<Button onClick={closeDrawer}>Cancel</Button>
|
|
217
|
+
<Button type="primary" onClick={() => form.submit()}>
|
|
218
|
+
Save
|
|
219
|
+
</Button>
|
|
220
|
+
</Space>
|
|
221
|
+
}
|
|
222
|
+
>
|
|
223
|
+
<Form form={form} layout="vertical" onFinish={saveProfile}>
|
|
224
|
+
<Form.Item name="tag" label="Tag" rules={[{ required: true, message: 'Tag is required' }]}>
|
|
225
|
+
<Input placeholder="default" disabled={editingRecord?.tag === 'default'} />
|
|
226
|
+
</Form.Item>
|
|
227
|
+
<Form.Item name="title" label="Title">
|
|
228
|
+
<Input />
|
|
229
|
+
</Form.Item>
|
|
230
|
+
<Form.Item name="description" label="Description">
|
|
231
|
+
<Input.TextArea rows={3} />
|
|
232
|
+
</Form.Item>
|
|
233
|
+
<Form.Item
|
|
234
|
+
name="settingsText"
|
|
235
|
+
label="Settings JSON"
|
|
236
|
+
rules={[{ required: true, message: 'Settings JSON is required' }]}
|
|
237
|
+
>
|
|
238
|
+
<Input.TextArea rows={12} spellCheck={false} />
|
|
239
|
+
</Form.Item>
|
|
240
|
+
<Form.Item name="enabled" label="Enabled" valuePropName="checked">
|
|
241
|
+
<Switch />
|
|
242
|
+
</Form.Item>
|
|
243
|
+
</Form>
|
|
244
|
+
</Drawer>
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
};
|
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Tabs } from 'antd';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
ApartmentOutlined,
|
|
5
|
+
BarChartOutlined,
|
|
6
|
+
CheckCircleOutlined,
|
|
7
|
+
CodeOutlined,
|
|
8
|
+
HistoryOutlined,
|
|
9
|
+
MonitorOutlined,
|
|
10
|
+
ProfileOutlined,
|
|
11
|
+
SettingOutlined,
|
|
12
|
+
} from '@ant-design/icons';
|
|
4
13
|
import { RulesTab } from './RulesTab';
|
|
5
14
|
import { TracingTab } from './TracingTab';
|
|
15
|
+
import { AgentRunsTab } from './AgentRunsTab';
|
|
16
|
+
import { HarnessProfilesTab } from './HarnessProfilesTab';
|
|
6
17
|
import { AIEmployeesProvider } from './AIEmployeesContext';
|
|
7
|
-
import { SkillManager, ExecutionHistory, SkillMetrics } from './skill-hub';
|
|
18
|
+
import { SkillManager, ExecutionHistory, SkillMetrics, LoopSettings } from './skill-hub';
|
|
8
19
|
|
|
9
20
|
const OrchestratorSettings: React.FC = () => {
|
|
10
21
|
return (
|
|
@@ -31,6 +42,24 @@ const OrchestratorSettings: React.FC = () => {
|
|
|
31
42
|
),
|
|
32
43
|
children: <TracingTab />,
|
|
33
44
|
},
|
|
45
|
+
{
|
|
46
|
+
key: 'agent-runs',
|
|
47
|
+
label: (
|
|
48
|
+
<span>
|
|
49
|
+
<ProfileOutlined /> Agent Runs
|
|
50
|
+
</span>
|
|
51
|
+
),
|
|
52
|
+
children: <AgentRunsTab />,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
key: 'harness-profiles',
|
|
56
|
+
label: (
|
|
57
|
+
<span>
|
|
58
|
+
<SettingOutlined /> Harness Profiles
|
|
59
|
+
</span>
|
|
60
|
+
),
|
|
61
|
+
children: <HarnessProfilesTab />,
|
|
62
|
+
},
|
|
34
63
|
{
|
|
35
64
|
key: 'skill-definitions',
|
|
36
65
|
label: (
|
|
@@ -49,6 +78,15 @@ const OrchestratorSettings: React.FC = () => {
|
|
|
49
78
|
),
|
|
50
79
|
children: <ExecutionHistory />,
|
|
51
80
|
},
|
|
81
|
+
{
|
|
82
|
+
key: 'skill-loop-settings',
|
|
83
|
+
label: (
|
|
84
|
+
<span>
|
|
85
|
+
<CheckCircleOutlined /> Skill Review Settings
|
|
86
|
+
</span>
|
|
87
|
+
),
|
|
88
|
+
children: <LoopSettings />,
|
|
89
|
+
},
|
|
52
90
|
{
|
|
53
91
|
key: 'skill-metrics',
|
|
54
92
|
label: (
|
package/src/client/RulesTab.tsx
CHANGED
|
@@ -40,6 +40,12 @@ const sanitizeToolPart = (value: string) => (value || '').replace(/[^a-zA-Z0-9_-
|
|
|
40
40
|
const expectedDelegateToolName = (leader: string, sub: string) =>
|
|
41
41
|
`delegate_${sanitizeToolPart(leader)}_to_${sanitizeToolPart(sub)}`;
|
|
42
42
|
const expectedDispatchToolName = (leader: string) => `dispatch_subagents_${sanitizeToolPart(leader)}`;
|
|
43
|
+
const controllerToolNames = [
|
|
44
|
+
'orchestrator_plan_goal',
|
|
45
|
+
'orchestrator_execute_plan',
|
|
46
|
+
'orchestrator_status',
|
|
47
|
+
'orchestrator_cancel',
|
|
48
|
+
];
|
|
43
49
|
|
|
44
50
|
export const RulesTab: React.FC = () => {
|
|
45
51
|
const api = useAPIClient();
|
|
@@ -58,12 +64,27 @@ export const RulesTab: React.FC = () => {
|
|
|
58
64
|
url: 'ai:listAllEnabledModels',
|
|
59
65
|
});
|
|
60
66
|
|
|
67
|
+
const { data: harnessProfilesData, loading: harnessLoading } = useRequest({
|
|
68
|
+
url: 'agentHarnessProfiles:list',
|
|
69
|
+
params: {
|
|
70
|
+
filter: { enabled: true },
|
|
71
|
+
sort: ['tag'],
|
|
72
|
+
pageSize: 100,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
61
76
|
const llmServices = React.useMemo(() => {
|
|
62
77
|
const raw = (llmServicesData as any)?.data ?? llmServicesData;
|
|
63
78
|
if (Array.isArray(raw)) return raw;
|
|
64
79
|
return Array.isArray(raw?.data) ? raw.data : [];
|
|
65
80
|
}, [llmServicesData]);
|
|
66
81
|
|
|
82
|
+
const harnessProfiles = React.useMemo(() => {
|
|
83
|
+
const raw = (harnessProfilesData as any)?.data ?? harnessProfilesData;
|
|
84
|
+
if (Array.isArray(raw)) return raw;
|
|
85
|
+
return Array.isArray(raw?.data) ? raw.data : [];
|
|
86
|
+
}, [harnessProfilesData]);
|
|
87
|
+
|
|
67
88
|
// P3 FIX: Use shared context instead of duplicate API call
|
|
68
89
|
const { employeeMap, skillsMap, refresh: refreshEmployees } = useAIEmployees();
|
|
69
90
|
const rules = React.useMemo(() => {
|
|
@@ -71,7 +92,7 @@ export const RulesTab: React.FC = () => {
|
|
|
71
92
|
return Array.isArray(rows) ? rows : [];
|
|
72
93
|
}, [data]);
|
|
73
94
|
|
|
74
|
-
const
|
|
95
|
+
const handleAddSkillsToEmployee = async (employeeUsername: string, toolNames: string[]) => {
|
|
75
96
|
try {
|
|
76
97
|
// Re-fetch the leader to merge its current skills (skillsMap may be stale).
|
|
77
98
|
const leaderResp = await api.request({
|
|
@@ -84,25 +105,31 @@ export const RulesTab: React.FC = () => {
|
|
|
84
105
|
return;
|
|
85
106
|
}
|
|
86
107
|
const existing = Array.isArray(leader.skillSettings?.skills) ? leader.skillSettings.skills : [];
|
|
87
|
-
|
|
88
|
-
|
|
108
|
+
const existingNames = new Set(existing.map((s: any) => (typeof s === 'string' ? s : s?.name)));
|
|
109
|
+
const missing = toolNames.filter((toolName) => !existingNames.has(toolName));
|
|
110
|
+
if (!missing.length) {
|
|
111
|
+
message.info('Skills already present.');
|
|
89
112
|
await refreshEmployees();
|
|
90
113
|
return;
|
|
91
114
|
}
|
|
92
|
-
const nextSkills = [...existing, { name
|
|
115
|
+
const nextSkills = [...existing, ...missing.map((name) => ({ name, autoCall: false }))];
|
|
93
116
|
await api.request({
|
|
94
117
|
url: 'aiEmployees:update',
|
|
95
118
|
method: 'put',
|
|
96
119
|
params: { filterByTk: employeeUsername },
|
|
97
120
|
data: { skillSettings: { ...(leader.skillSettings || {}), skills: nextSkills } },
|
|
98
121
|
});
|
|
99
|
-
message.success(`Added
|
|
122
|
+
message.success(`Added ${missing.length} skill${missing.length > 1 ? 's' : ''} to ${employeeUsername}.`);
|
|
100
123
|
await refreshEmployees();
|
|
101
124
|
} catch (e: any) {
|
|
102
125
|
message.error(`Auto-assign failed: ${e?.message || 'unknown error'}`);
|
|
103
126
|
}
|
|
104
127
|
};
|
|
105
128
|
|
|
129
|
+
const handleAddSkillToEmployee = async (employeeUsername: string, toolName: string) => {
|
|
130
|
+
await handleAddSkillsToEmployee(employeeUsername, [toolName]);
|
|
131
|
+
};
|
|
132
|
+
|
|
106
133
|
const handleAutoAssignSkill = async (record: any) => {
|
|
107
134
|
await handleAddSkillToEmployee(
|
|
108
135
|
record.leaderUsername,
|
|
@@ -154,7 +181,7 @@ export const RulesTab: React.FC = () => {
|
|
|
154
181
|
form.setFieldsValue(record);
|
|
155
182
|
} else {
|
|
156
183
|
form.resetFields();
|
|
157
|
-
form.setFieldsValue({ enabled: true, maxDepth: 1, timeout: 120000, recursionLimit: 50 });
|
|
184
|
+
form.setFieldsValue({ enabled: true, maxDepth: 1, timeout: 120000, recursionLimit: 50, harnessTag: 'default' });
|
|
158
185
|
}
|
|
159
186
|
setVisible(true);
|
|
160
187
|
};
|
|
@@ -229,6 +256,13 @@ export const RulesTab: React.FC = () => {
|
|
|
229
256
|
key: 'subAgentUsername',
|
|
230
257
|
render: (username: string) => <Tag color="green">{employeeMap.get(username) || username}</Tag>,
|
|
231
258
|
},
|
|
259
|
+
{
|
|
260
|
+
title: 'Harness',
|
|
261
|
+
dataIndex: 'harnessTag',
|
|
262
|
+
key: 'harnessTag',
|
|
263
|
+
width: 120,
|
|
264
|
+
render: (tag: string) => <Tag color="purple">{tag || 'default'}</Tag>,
|
|
265
|
+
},
|
|
232
266
|
{
|
|
233
267
|
title: 'Max Depth',
|
|
234
268
|
dataIndex: 'maxDepth',
|
|
@@ -366,6 +400,17 @@ export const RulesTab: React.FC = () => {
|
|
|
366
400
|
.filter(Boolean) as Array<{ leaderUsername: string; toolName: string; count: number }>;
|
|
367
401
|
}, [groupedRules, skillsMap]);
|
|
368
402
|
|
|
403
|
+
const missingControllerSkills = React.useMemo(() => {
|
|
404
|
+
return groupedRules
|
|
405
|
+
.map((group) => {
|
|
406
|
+
const leaderSkills = skillsMap.get(group.leaderUsername);
|
|
407
|
+
if (!leaderSkills) return null;
|
|
408
|
+
const missing = controllerToolNames.filter((toolName) => !leaderSkills.has(toolName));
|
|
409
|
+
return missing.length ? { leaderUsername: group.leaderUsername, missing } : null;
|
|
410
|
+
})
|
|
411
|
+
.filter(Boolean) as Array<{ leaderUsername: string; missing: string[] }>;
|
|
412
|
+
}, [groupedRules, skillsMap]);
|
|
413
|
+
|
|
369
414
|
return (
|
|
370
415
|
<div>
|
|
371
416
|
<Alert
|
|
@@ -397,6 +442,39 @@ export const RulesTab: React.FC = () => {
|
|
|
397
442
|
/>
|
|
398
443
|
)}
|
|
399
444
|
|
|
445
|
+
{missingControllerSkills.length > 0 && (
|
|
446
|
+
<Alert
|
|
447
|
+
type="warning"
|
|
448
|
+
showIcon
|
|
449
|
+
style={{ marginBottom: 16 }}
|
|
450
|
+
message={`${missingControllerSkills.length} leader${
|
|
451
|
+
missingControllerSkills.length > 1 ? 's' : ''
|
|
452
|
+
} missing orchestrator controller tools`}
|
|
453
|
+
description={
|
|
454
|
+
<Space direction="vertical" size={6}>
|
|
455
|
+
<Text type="secondary">
|
|
456
|
+
Leaders need the orchestrator controller tools to create an approval-first plan and execute it after
|
|
457
|
+
the user accepts the card.
|
|
458
|
+
</Text>
|
|
459
|
+
{missingControllerSkills.map(({ leaderUsername, missing }) => (
|
|
460
|
+
<Space key={leaderUsername} size={8} wrap>
|
|
461
|
+
<Tag color="blue">{employeeMap.get(leaderUsername) || leaderUsername}</Tag>
|
|
462
|
+
<Text type="secondary">{missing.length} missing</Text>
|
|
463
|
+
<Button
|
|
464
|
+
type="link"
|
|
465
|
+
size="small"
|
|
466
|
+
icon={<ThunderboltOutlined />}
|
|
467
|
+
onClick={() => handleAddSkillsToEmployee(leaderUsername, missing)}
|
|
468
|
+
>
|
|
469
|
+
Auto-add
|
|
470
|
+
</Button>
|
|
471
|
+
</Space>
|
|
472
|
+
))}
|
|
473
|
+
</Space>
|
|
474
|
+
}
|
|
475
|
+
/>
|
|
476
|
+
)}
|
|
477
|
+
|
|
400
478
|
{missingDispatchSkills.length > 0 && (
|
|
401
479
|
<Alert
|
|
402
480
|
type="warning"
|
|
@@ -560,6 +638,25 @@ export const RulesTab: React.FC = () => {
|
|
|
560
638
|
<InputNumber min={5} max={200} step={5} style={{ width: '100%' }} />
|
|
561
639
|
</Form.Item>
|
|
562
640
|
|
|
641
|
+
<Form.Item
|
|
642
|
+
name="harnessTag"
|
|
643
|
+
label="Harness Profile"
|
|
644
|
+
tooltip="Profile tag used by plan approval, controller limits, and orchestration policy."
|
|
645
|
+
>
|
|
646
|
+
<Select
|
|
647
|
+
loading={harnessLoading}
|
|
648
|
+
options={[
|
|
649
|
+
{ label: 'default', value: 'default' },
|
|
650
|
+
...harnessProfiles
|
|
651
|
+
.filter((profile: any) => profile.tag !== 'default')
|
|
652
|
+
.map((profile: any) => ({
|
|
653
|
+
label: profile.title ? `${profile.tag} - ${profile.title}` : profile.tag,
|
|
654
|
+
value: profile.tag,
|
|
655
|
+
})),
|
|
656
|
+
]}
|
|
657
|
+
/>
|
|
658
|
+
</Form.Item>
|
|
659
|
+
|
|
563
660
|
<Form.Item
|
|
564
661
|
name="llmService"
|
|
565
662
|
label="Override LLM Service"
|
package/src/client/plugin.tsx
CHANGED
|
@@ -1,54 +1,27 @@
|
|
|
1
|
-
import { Plugin } from '@nocobase/client';
|
|
2
|
-
import { OrchestratorSettings } from './OrchestratorSettings';
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
private async registerSkillUiCards() {
|
|
30
|
-
const toolsManager = (this as any).app.aiManager?.toolsManager;
|
|
31
|
-
if (!toolsManager) return;
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const { data } = await (this as any).app.apiClient.request({
|
|
35
|
-
url: 'skillDefinitions:list',
|
|
36
|
-
params: {
|
|
37
|
-
filter: { enabled: true },
|
|
38
|
-
fields: ['name', 'autoCall', 'interactionSchema'],
|
|
39
|
-
pageSize: 200,
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
const list = (data as any)?.data ?? [];
|
|
43
|
-
for (const s of list) {
|
|
44
|
-
if (s.autoCall) continue;
|
|
45
|
-
if (!parseJsonText(s.interactionSchema, null)) continue;
|
|
46
|
-
toolsManager.registerTools(`skill_hub_${sanitize(s.name)}`, { ui: { card: SkillHubCard } });
|
|
47
|
-
}
|
|
48
|
-
} catch {
|
|
49
|
-
// user without ACL or backend unavailable — skip silently
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export default PluginAgentOrchestratorClient;
|
|
1
|
+
import { Plugin } from '@nocobase/client';
|
|
2
|
+
import { OrchestratorSettings } from './OrchestratorSettings';
|
|
3
|
+
import { InteractionSchemasProvider } from './skill-hub/tools/InteractionSchemasProvider';
|
|
4
|
+
import { registerSkillLoopCards } from './skill-hub/tools/registerSkillLoopCards';
|
|
5
|
+
import { registerOrchestratorCards } from './tools/registerOrchestratorCards';
|
|
6
|
+
|
|
7
|
+
export class PluginAgentOrchestratorClient extends Plugin {
|
|
8
|
+
async load() {
|
|
9
|
+
(this as any).app.use(InteractionSchemasProvider);
|
|
10
|
+
|
|
11
|
+
// Register under the "AI" settings group for consistency with other AI plugins
|
|
12
|
+
(this as any).app.pluginSettingsManager.add('ai.orchestrator', {
|
|
13
|
+
title: 'Agent Orchestrator',
|
|
14
|
+
icon: 'ApartmentOutlined',
|
|
15
|
+
Component: OrchestratorSettings,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
await this.registerSkillUiCards();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private async registerSkillUiCards() {
|
|
22
|
+
await registerOrchestratorCards((this as any).app);
|
|
23
|
+
await registerSkillLoopCards((this as any).app);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default PluginAgentOrchestratorClient;
|