plugin-agent-orchestrator 1.0.17 → 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.
Files changed (139) hide show
  1. package/dist/client/AIEmployeeSelect.d.ts +11 -0
  2. package/dist/client/AIEmployeesContext.d.ts +30 -0
  3. package/dist/client/AgentRunsTab.d.ts +2 -0
  4. package/dist/client/HarnessProfilesTab.d.ts +2 -0
  5. package/dist/client/OrchestratorSettings.d.ts +3 -0
  6. package/dist/client/RulesTab.d.ts +2 -0
  7. package/dist/client/TracingTab.d.ts +2 -0
  8. package/dist/client/index.d.ts +1 -0
  9. package/dist/client/index.js +1 -1
  10. package/dist/client/plugin.d.ts +6 -0
  11. package/dist/client/skill-hub/components/ExecutionHistory.d.ts +2 -0
  12. package/dist/client/skill-hub/components/ExecutionProgress.d.ts +20 -0
  13. package/dist/client/skill-hub/components/GitSkillImport.d.ts +7 -0
  14. package/dist/client/skill-hub/components/LoopSettings.d.ts +2 -0
  15. package/dist/client/skill-hub/components/SkillEditor.d.ts +7 -0
  16. package/dist/client/skill-hub/components/SkillManager.d.ts +2 -0
  17. package/dist/client/skill-hub/components/SkillMetrics.d.ts +2 -0
  18. package/dist/client/skill-hub/components/SkillTestPanel.d.ts +7 -0
  19. package/dist/client/skill-hub/index.d.ts +11 -0
  20. package/dist/client/skill-hub/locale.d.ts +3 -0
  21. package/dist/client/skill-hub/tools/InteractionSchemasProvider.d.ts +6 -0
  22. package/dist/client/skill-hub/tools/SkillHubCard.d.ts +3 -0
  23. package/dist/client/skill-hub/tools/loopTemplates.d.ts +22 -0
  24. package/dist/client/skill-hub/tools/registerSkillLoopCards.d.ts +1 -0
  25. package/dist/client/skill-hub/utils/jsonFields.d.ts +3 -0
  26. package/dist/client/tools/PlanApprovalCard.d.ts +3 -0
  27. package/dist/client/tools/registerOrchestratorCards.d.ts +1 -0
  28. package/dist/externalVersion.js +6 -6
  29. package/dist/index.d.ts +2 -0
  30. package/dist/server/collections/agent-execution-spans.d.ts +9 -0
  31. package/dist/server/collections/agent-harness-profiles.d.ts +2 -0
  32. package/dist/server/collections/agent-harness-profiles.js +89 -0
  33. package/dist/server/collections/agent-loop-events.d.ts +2 -0
  34. package/dist/server/collections/agent-loop-events.js +101 -0
  35. package/dist/server/collections/agent-loop-runs.d.ts +2 -0
  36. package/dist/server/collections/agent-loop-runs.js +188 -0
  37. package/dist/server/collections/agent-loop-steps.d.ts +2 -0
  38. package/dist/server/collections/agent-loop-steps.js +174 -0
  39. package/dist/server/collections/orchestrator-config.d.ts +2 -0
  40. package/dist/server/collections/orchestrator-config.js +7 -0
  41. package/dist/server/collections/orchestrator-logs.d.ts +8 -0
  42. package/dist/server/collections/skill-definitions.d.ts +3 -0
  43. package/dist/server/collections/skill-executions.d.ts +3 -0
  44. package/dist/server/collections/skill-executions.js +12 -0
  45. package/dist/server/collections/skill-loop-configs.d.ts +3 -0
  46. package/dist/server/collections/skill-loop-configs.js +94 -0
  47. package/dist/server/collections/skill-worker-configs.d.ts +3 -0
  48. package/dist/server/index.d.ts +1 -0
  49. package/dist/server/migrations/20260423000000-add-progress-fields.d.ts +4 -0
  50. package/dist/server/migrations/20260425000000-add-interaction-schema.d.ts +4 -0
  51. package/dist/server/migrations/20260427000000-add-tracing-detail-fields.d.ts +7 -0
  52. package/dist/server/migrations/20260427000000-change-packages-to-text.d.ts +4 -0
  53. package/dist/server/migrations/20260427000001-change-other-json-to-text.d.ts +4 -0
  54. package/dist/server/migrations/20260429000000-add-llm-fields.d.ts +7 -0
  55. package/dist/server/migrations/20260429000000-fix-inputargs-json-to-text.d.ts +16 -0
  56. package/dist/server/migrations/20260503000000-add-orchestrator-trace-fields.d.ts +7 -0
  57. package/dist/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.d.ts +7 -0
  58. package/dist/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.js +55 -0
  59. package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.d.ts +12 -0
  60. package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.js +162 -0
  61. package/dist/server/plugin.d.ts +16 -0
  62. package/dist/server/plugin.js +13 -0
  63. package/dist/server/resources/agent-loop.d.ts +3 -0
  64. package/dist/server/resources/agent-loop.js +205 -0
  65. package/dist/server/resources/tracing.d.ts +7 -0
  66. package/dist/server/services/AgentHarness.d.ts +42 -0
  67. package/dist/server/services/AgentHarness.js +565 -0
  68. package/dist/server/services/AgentLoopController.d.ts +205 -0
  69. package/dist/server/services/AgentLoopController.js +940 -0
  70. package/dist/server/services/AgentLoopRepository.d.ts +20 -0
  71. package/dist/server/services/AgentLoopRepository.js +210 -0
  72. package/dist/server/services/AgentLoopService.d.ts +149 -0
  73. package/dist/server/services/AgentLoopService.js +133 -0
  74. package/dist/server/services/AgentPlanValidator.d.ts +4 -0
  75. package/dist/server/services/AgentPlanValidator.js +99 -0
  76. package/dist/server/services/AgentPlannerService.d.ts +8 -0
  77. package/dist/server/services/AgentPlannerService.js +119 -0
  78. package/dist/server/services/AgentRegistryService.d.ts +13 -0
  79. package/dist/server/services/AgentRegistryService.js +178 -0
  80. package/dist/server/services/CodeValidator.d.ts +32 -0
  81. package/dist/server/services/ExecutionSpanService.d.ts +46 -0
  82. package/dist/server/services/FileManager.d.ts +28 -0
  83. package/dist/server/services/SandboxRunner.d.ts +41 -0
  84. package/dist/server/services/SkillManager.d.ts +6 -0
  85. package/dist/server/services/SkillRepositoryService.d.ts +22 -0
  86. package/dist/server/services/WorkerEnvManager.d.ts +26 -0
  87. package/dist/server/skill-hub/actions/git-import.d.ts +21 -0
  88. package/dist/server/skill-hub/mcp/McpController.d.ts +15 -0
  89. package/dist/server/skill-hub/plugin.d.ts +61 -0
  90. package/dist/server/skill-hub/plugin.js +137 -54
  91. package/dist/server/skill-hub/tasks/SkillExecutionTask.d.ts +16 -0
  92. package/dist/server/skill-hub/utils/json-fields.d.ts +7 -0
  93. package/dist/server/tools/agent-loop.d.ts +235 -0
  94. package/dist/server/tools/agent-loop.js +406 -0
  95. package/dist/server/tools/delegate-task.d.ts +19 -0
  96. package/dist/server/tools/delegate-task.js +19 -368
  97. package/dist/server/tools/external-rag-search.d.ts +42 -0
  98. package/dist/server/tools/orchestrator-plan.d.ts +205 -0
  99. package/dist/server/tools/orchestrator-plan.js +291 -0
  100. package/dist/server/tools/skill-execute.d.ts +36 -0
  101. package/dist/server/tools/skill-execute.js +2 -0
  102. package/package.json +1 -1
  103. package/src/client/AgentRunsTab.tsx +764 -0
  104. package/src/client/HarnessProfilesTab.tsx +247 -0
  105. package/src/client/OrchestratorSettings.tsx +40 -2
  106. package/src/client/RulesTab.tsx +103 -6
  107. package/src/client/plugin.tsx +27 -54
  108. package/src/client/skill-hub/components/LoopSettings.tsx +331 -0
  109. package/src/client/skill-hub/index.tsx +51 -75
  110. package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +56 -16
  111. package/src/client/skill-hub/tools/SkillHubCard.tsx +35 -4
  112. package/src/client/skill-hub/tools/loopTemplates.ts +52 -0
  113. package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -0
  114. package/src/client/tools/PlanApprovalCard.tsx +175 -0
  115. package/src/client/tools/registerOrchestratorCards.ts +7 -0
  116. package/src/server/collections/agent-harness-profiles.ts +59 -0
  117. package/src/server/collections/agent-loop-events.ts +71 -0
  118. package/src/server/collections/agent-loop-runs.ts +158 -0
  119. package/src/server/collections/agent-loop-steps.ts +144 -0
  120. package/src/server/collections/orchestrator-config.ts +7 -0
  121. package/src/server/collections/skill-executions.ts +63 -51
  122. package/src/server/collections/skill-loop-configs.ts +65 -0
  123. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -0
  124. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -0
  125. package/src/server/plugin.ts +15 -0
  126. package/src/server/resources/agent-loop.ts +183 -0
  127. package/src/server/services/AgentHarness.ts +663 -0
  128. package/src/server/services/AgentLoopController.ts +1128 -0
  129. package/src/server/services/AgentLoopRepository.ts +194 -0
  130. package/src/server/services/AgentLoopService.ts +161 -0
  131. package/src/server/services/AgentPlanValidator.ts +73 -0
  132. package/src/server/services/AgentPlannerService.ts +93 -0
  133. package/src/server/services/AgentRegistryService.ts +169 -0
  134. package/src/server/services/ExecutionSpanService.ts +2 -0
  135. package/src/server/skill-hub/plugin.ts +881 -771
  136. package/src/server/tools/agent-loop.ts +399 -0
  137. package/src/server/tools/delegate-task.ts +23 -485
  138. package/src/server/tools/orchestrator-plan.ts +279 -0
  139. 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 { ApartmentOutlined, BarChartOutlined, CodeOutlined, HistoryOutlined, MonitorOutlined } from '@ant-design/icons';
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: (
@@ -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 handleAddSkillToEmployee = async (employeeUsername: string, toolName: string) => {
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
- if (existing.some((s: any) => (typeof s === 'string' ? s : s?.name) === toolName)) {
88
- message.info('Skill already present.');
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: toolName, autoCall: false }];
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 "${toolName}" to ${employeeUsername}'s skills.`);
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"
@@ -1,54 +1,27 @@
1
- import { Plugin } from '@nocobase/client';
2
- import { OrchestratorSettings } from './OrchestratorSettings';
3
-
4
- import { InteractionSchemasProvider } from './skill-hub/tools/InteractionSchemasProvider';
5
- import { SkillHubCard } from './skill-hub/tools/SkillHubCard';
6
- import { parseJsonText } from './skill-hub/utils/jsonFields';
7
-
8
- const sanitize = (name: string) =>
9
- name
10
- .toLowerCase()
11
- .replace(/[^a-z0-9_]/g, '_')
12
- .replace(/_+/g, '_')
13
- .replace(/^_|_$/g, '');
14
-
15
- export class PluginAgentOrchestratorClient extends Plugin {
16
- async load() {
17
- (this as any).app.use(InteractionSchemasProvider);
18
-
19
- // Register under the "AI" settings group for consistency with other AI plugins
20
- (this as any).app.pluginSettingsManager.add('ai.orchestrator', {
21
- title: 'Agent Orchestrator',
22
- icon: 'ApartmentOutlined',
23
- Component: OrchestratorSettings,
24
- });
25
-
26
- await this.registerSkillUiCards();
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;