plugin-agent-orchestrator 1.0.17 → 1.0.19

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 (141) 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 +152 -54
  91. package/dist/server/skill-hub/tasks/SkillExecutionTask.d.ts +16 -0
  92. package/dist/server/skill-hub/tasks/SkillExecutionTask.js +15 -0
  93. package/dist/server/skill-hub/utils/json-fields.d.ts +7 -0
  94. package/dist/server/tools/agent-loop.d.ts +235 -0
  95. package/dist/server/tools/agent-loop.js +406 -0
  96. package/dist/server/tools/delegate-task.d.ts +19 -0
  97. package/dist/server/tools/delegate-task.js +19 -368
  98. package/dist/server/tools/external-rag-search.d.ts +42 -0
  99. package/dist/server/tools/orchestrator-plan.d.ts +205 -0
  100. package/dist/server/tools/orchestrator-plan.js +291 -0
  101. package/dist/server/tools/skill-execute.d.ts +36 -0
  102. package/dist/server/tools/skill-execute.js +2 -0
  103. package/package.json +1 -1
  104. package/src/client/AgentRunsTab.tsx +764 -0
  105. package/src/client/HarnessProfilesTab.tsx +247 -0
  106. package/src/client/OrchestratorSettings.tsx +40 -2
  107. package/src/client/RulesTab.tsx +103 -6
  108. package/src/client/plugin.tsx +27 -54
  109. package/src/client/skill-hub/components/LoopSettings.tsx +331 -0
  110. package/src/client/skill-hub/index.tsx +51 -75
  111. package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +56 -16
  112. package/src/client/skill-hub/tools/SkillHubCard.tsx +35 -4
  113. package/src/client/skill-hub/tools/loopTemplates.ts +52 -0
  114. package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -0
  115. package/src/client/tools/PlanApprovalCard.tsx +175 -0
  116. package/src/client/tools/registerOrchestratorCards.ts +7 -0
  117. package/src/server/collections/agent-harness-profiles.ts +59 -0
  118. package/src/server/collections/agent-loop-events.ts +71 -0
  119. package/src/server/collections/agent-loop-runs.ts +158 -0
  120. package/src/server/collections/agent-loop-steps.ts +144 -0
  121. package/src/server/collections/orchestrator-config.ts +7 -0
  122. package/src/server/collections/skill-executions.ts +63 -51
  123. package/src/server/collections/skill-loop-configs.ts +65 -0
  124. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -0
  125. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -0
  126. package/src/server/plugin.ts +15 -0
  127. package/src/server/resources/agent-loop.ts +183 -0
  128. package/src/server/services/AgentHarness.ts +663 -0
  129. package/src/server/services/AgentLoopController.ts +1128 -0
  130. package/src/server/services/AgentLoopRepository.ts +194 -0
  131. package/src/server/services/AgentLoopService.ts +161 -0
  132. package/src/server/services/AgentPlanValidator.ts +73 -0
  133. package/src/server/services/AgentPlannerService.ts +93 -0
  134. package/src/server/services/AgentRegistryService.ts +169 -0
  135. package/src/server/services/ExecutionSpanService.ts +2 -0
  136. package/src/server/skill-hub/plugin.ts +897 -771
  137. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +15 -0
  138. package/src/server/tools/agent-loop.ts +399 -0
  139. package/src/server/tools/delegate-task.ts +23 -485
  140. package/src/server/tools/orchestrator-plan.ts +279 -0
  141. package/src/server/tools/skill-execute.ts +68 -64
@@ -0,0 +1,331 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import {
3
+ Button,
4
+ Card,
5
+ Form,
6
+ Input,
7
+ List,
8
+ message,
9
+ Modal,
10
+ Popconfirm,
11
+ Select,
12
+ Space,
13
+ Switch,
14
+ Tag,
15
+ Tooltip,
16
+ Typography,
17
+ } from 'antd';
18
+ import { DeleteOutlined, EditOutlined, PlusOutlined } from '@ant-design/icons';
19
+ import { useAPIClient, useApp } from '@nocobase/client';
20
+ import { useT } from '../locale';
21
+ import { formatJsonText, parseJsonText, stringifyJsonText } from '../utils/jsonFields';
22
+ import { getLoopTemplate, LOOP_TEMPLATES } from '../tools/loopTemplates';
23
+ import { registerSkillLoopCards } from '../tools/registerSkillLoopCards';
24
+
25
+ const { TextArea } = Input;
26
+
27
+ const extractList = (data: any) => {
28
+ const value = data?.data?.data ?? data?.data ?? data ?? [];
29
+ return Array.isArray(value) ? value : [];
30
+ };
31
+
32
+ export const LoopSettings: React.FC = () => {
33
+ const api = useAPIClient();
34
+ const app = useApp();
35
+ const t = useT();
36
+ const [form] = Form.useForm();
37
+ const [skills, setSkills] = useState<any[]>([]);
38
+ const [configs, setConfigs] = useState<any[]>([]);
39
+ const [loading, setLoading] = useState(false);
40
+ const [editorVisible, setEditorVisible] = useState(false);
41
+ const [editingConfig, setEditingConfig] = useState<any>(null);
42
+
43
+ const skillsById = useMemo(() => new Map(skills.map((skill) => [String(skill.id), skill])), [skills]);
44
+
45
+ const fetchData = useCallback(async () => {
46
+ setLoading(true);
47
+ try {
48
+ const [skillsResponse, configsResponse] = await Promise.all([
49
+ api.request({
50
+ url: 'skillDefinitions:list',
51
+ params: {
52
+ filter: { enabled: true },
53
+ fields: ['id', 'name', 'title', 'language', 'autoCall'],
54
+ pageSize: 500,
55
+ },
56
+ }),
57
+ api.request({
58
+ url: 'skillLoopConfigs:list',
59
+ params: {
60
+ fields: ['id', 'skillId', 'enabled', 'title', 'templateKey', 'prompt', 'schema', 'config', 'updatedAt'],
61
+ sort: ['-updatedAt'],
62
+ pageSize: 500,
63
+ },
64
+ }),
65
+ ]);
66
+ setSkills(extractList(skillsResponse.data));
67
+ setConfigs(extractList(configsResponse.data));
68
+ } catch {
69
+ message.error(t('Failed to load skill review settings'));
70
+ } finally {
71
+ setLoading(false);
72
+ }
73
+ }, [api, t]);
74
+
75
+ useEffect(() => {
76
+ fetchData();
77
+ }, [fetchData]);
78
+
79
+ const openCreate = () => {
80
+ const template = getLoopTemplate('confirm');
81
+ setEditingConfig(null);
82
+ form.resetFields();
83
+ form.setFieldsValue({
84
+ enabled: true,
85
+ templateKey: template.key,
86
+ prompt: template.schema.prompt,
87
+ schema: formatJsonText(template.schema),
88
+ config: '',
89
+ });
90
+ setEditorVisible(true);
91
+ };
92
+
93
+ const openEdit = (record: any) => {
94
+ const template = getLoopTemplate(record.templateKey);
95
+ setEditingConfig(record);
96
+ form.setFieldsValue({
97
+ ...record,
98
+ templateKey: record.templateKey || template.key,
99
+ prompt: record.prompt || template.schema.prompt,
100
+ schema: formatJsonText(record.schema || template.schema),
101
+ config: formatJsonText(record.config, null),
102
+ });
103
+ setEditorVisible(true);
104
+ };
105
+
106
+ const closeEditor = () => {
107
+ setEditorVisible(false);
108
+ setEditingConfig(null);
109
+ form.resetFields();
110
+ };
111
+
112
+ const notifyLoopSettingsChanged = useCallback(async () => {
113
+ await registerSkillLoopCards(app);
114
+ window.dispatchEvent(new Event('skill-hub-loop-settings-changed'));
115
+ }, [app]);
116
+
117
+ const handleTemplateChange = (templateKey: string) => {
118
+ const template = getLoopTemplate(templateKey);
119
+ form.setFieldsValue({
120
+ prompt: template.schema.prompt,
121
+ schema: formatJsonText(template.schema),
122
+ });
123
+ };
124
+
125
+ const handleSave = async () => {
126
+ try {
127
+ const values = await form.validateFields();
128
+ const schema = parseJsonText<any>(values.schema, undefined);
129
+ if (!schema || typeof schema !== 'object') {
130
+ message.error(t('Invalid JSON in Review Schema'));
131
+ return;
132
+ }
133
+
134
+ const config = parseJsonText<any>(values.config, undefined);
135
+ if (values.config && config === undefined) {
136
+ message.error(t('Invalid JSON in Review Config'));
137
+ return;
138
+ }
139
+
140
+ const schemaWithPrompt = {
141
+ ...schema,
142
+ prompt: values.prompt || schema.prompt,
143
+ };
144
+ const data = {
145
+ ...values,
146
+ schema: stringifyJsonText(schemaWithPrompt),
147
+ config: values.config ? stringifyJsonText(config) : null,
148
+ };
149
+
150
+ if (editingConfig) {
151
+ await api.request({
152
+ url: 'skillLoopConfigs:update',
153
+ method: 'POST',
154
+ params: { filterByTk: editingConfig.id },
155
+ data,
156
+ });
157
+ } else {
158
+ await api.request({
159
+ url: 'skillLoopConfigs:create',
160
+ method: 'POST',
161
+ data,
162
+ });
163
+ }
164
+
165
+ message.success(t(editingConfig ? 'Skill review setting updated' : 'Skill review setting created'));
166
+ closeEditor();
167
+ fetchData();
168
+ void notifyLoopSettingsChanged();
169
+ } catch (err: any) {
170
+ if (err?.errorFields) return;
171
+ message.error(t('Failed to save skill review setting'));
172
+ }
173
+ };
174
+
175
+ const handleDelete = async (id: number) => {
176
+ try {
177
+ await api.request({ url: 'skillLoopConfigs:destroy', method: 'POST', params: { filterByTk: id } });
178
+ message.success(t('Deleted'));
179
+ fetchData();
180
+ void notifyLoopSettingsChanged();
181
+ } catch {
182
+ message.error(t('Failed to delete'));
183
+ }
184
+ };
185
+
186
+ const handleToggleEnabled = async (record: any) => {
187
+ try {
188
+ await api.request({
189
+ url: 'skillLoopConfigs:update',
190
+ method: 'POST',
191
+ params: { filterByTk: record.id },
192
+ data: { enabled: !record.enabled },
193
+ });
194
+ fetchData();
195
+ void notifyLoopSettingsChanged();
196
+ } catch {
197
+ message.error(t('Failed to update'));
198
+ }
199
+ };
200
+
201
+ return (
202
+ <Card
203
+ title={t('Skill Review Settings')}
204
+ extra={
205
+ <Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
206
+ {t('New Review')}
207
+ </Button>
208
+ }
209
+ >
210
+ <List
211
+ loading={loading}
212
+ dataSource={configs}
213
+ grid={{ gutter: 16, xs: 1, sm: 2, md: 3, lg: 3, xl: 4, xxl: 4 }}
214
+ renderItem={(config) => {
215
+ const skill = skillsById.get(String(config.skillId));
216
+ const template = getLoopTemplate(config.templateKey);
217
+ return (
218
+ <List.Item>
219
+ <Card
220
+ size="small"
221
+ title={
222
+ <Typography.Text ellipsis>
223
+ {config.title || skill?.title || skill?.name || t('Unknown skill')}
224
+ </Typography.Text>
225
+ }
226
+ extra={
227
+ <Tag color={config.enabled ? 'green' : 'default'}>
228
+ {config.enabled ? t('Enabled') : t('Disabled')}
229
+ </Tag>
230
+ }
231
+ actions={[
232
+ <Tooltip key="edit" title={t('Edit')}>
233
+ <EditOutlined onClick={() => openEdit(config)} />
234
+ </Tooltip>,
235
+ <Popconfirm key="delete" title={t('Delete?')} onConfirm={() => handleDelete(config.id)}>
236
+ <Tooltip title={t('Delete')}>
237
+ <DeleteOutlined style={{ color: 'red' }} />
238
+ </Tooltip>
239
+ </Popconfirm>,
240
+ ]}
241
+ style={{ boxShadow: '0 2px 8px rgba(0,0,0,0.05)', borderRadius: 8 }}
242
+ >
243
+ <Space direction="vertical" size={8} style={{ width: '100%' }}>
244
+ <Typography.Text type="secondary" style={{ fontSize: 13 }}>
245
+ {skill?.name || `skillId=${config.skillId}`}
246
+ </Typography.Text>
247
+ <Tag color="blue" style={{ width: 'fit-content' }}>
248
+ {template.title}
249
+ </Tag>
250
+ <Typography.Paragraph
251
+ type="secondary"
252
+ ellipsis={{ rows: 2 }}
253
+ style={{ minHeight: 44, marginBottom: 0, fontSize: 13 }}
254
+ >
255
+ {config.prompt || template.schema.prompt}
256
+ </Typography.Paragraph>
257
+ <Space size={4}>
258
+ <Switch checked={config.enabled} onChange={() => handleToggleEnabled(config)} size="small" />
259
+ <span style={{ fontSize: 12 }}>{config.enabled ? t('Enabled') : t('Disabled')}</span>
260
+ </Space>
261
+ </Space>
262
+ </Card>
263
+ </List.Item>
264
+ );
265
+ }}
266
+ />
267
+
268
+ <Modal
269
+ open={editorVisible}
270
+ title={editingConfig ? t('Edit Skill Review Setting') : t('New Skill Review Setting')}
271
+ onCancel={closeEditor}
272
+ onOk={handleSave}
273
+ width={760}
274
+ destroyOnClose
275
+ >
276
+ <Form form={form} layout="vertical">
277
+ <Form.Item name="enabled" valuePropName="checked" label={t('Enabled')}>
278
+ <Switch />
279
+ </Form.Item>
280
+
281
+ <Form.Item name="skillId" label={t('Skill')} rules={[{ required: true }]}>
282
+ <Select
283
+ showSearch
284
+ optionFilterProp="label"
285
+ placeholder={t('Select a skill')}
286
+ options={skills.map((skill) => ({
287
+ value: skill.id,
288
+ label: `${skill.title || skill.name} (${skill.name})`,
289
+ }))}
290
+ />
291
+ </Form.Item>
292
+
293
+ <Form.Item name="title" label={t('Title')}>
294
+ <Input placeholder={t('Optional display title')} />
295
+ </Form.Item>
296
+
297
+ <Form.Item name="templateKey" label={t('Review Template')} rules={[{ required: true }]}>
298
+ <Select onChange={handleTemplateChange}>
299
+ {LOOP_TEMPLATES.map((template) => (
300
+ <Select.Option key={template.key} value={template.key}>
301
+ {template.title} - {template.description}
302
+ </Select.Option>
303
+ ))}
304
+ </Select>
305
+ </Form.Item>
306
+
307
+ <Form.Item name="prompt" label={t('Prompt')} rules={[{ required: true }]}>
308
+ <TextArea rows={3} />
309
+ </Form.Item>
310
+
311
+ <Form.Item
312
+ name="schema"
313
+ label={t('Review Schema (JSON)')}
314
+ rules={[{ required: true }]}
315
+ extra={t('Standard interaction schema. Supported types: confirm, form, select.')}
316
+ >
317
+ <TextArea rows={10} style={{ fontFamily: 'monospace', fontSize: 13 }} />
318
+ </Form.Item>
319
+
320
+ <Form.Item name="config" label={t('Review Config (optional JSON)')}>
321
+ <TextArea
322
+ rows={4}
323
+ style={{ fontFamily: 'monospace', fontSize: 13 }}
324
+ placeholder={'{\n "maxRetries": 1\n}'}
325
+ />
326
+ </Form.Item>
327
+ </Form>
328
+ </Modal>
329
+ </Card>
330
+ );
331
+ };
@@ -1,75 +1,51 @@
1
- import { Plugin } from '@nocobase/client';
2
- import { SkillManager } from './components/SkillManager';
3
- import { ExecutionHistory } from './components/ExecutionHistory';
4
-
5
- import { SkillMetrics } from './components/SkillMetrics';
6
- import { InteractionSchemasProvider } from './tools/InteractionSchemasProvider';
7
- import { SkillHubCard } from './tools/SkillHubCard';
8
- import { parseJsonText } from './utils/jsonFields';
9
-
10
- const sanitize = (name: string) =>
11
- name
12
- .toLowerCase()
13
- .replace(/[^a-z0-9_]/g, '_')
14
- .replace(/_+/g, '_')
15
- .replace(/^_|_$/g, '');
16
-
17
- export class PluginSkillHubClient extends Plugin {
18
- async load() {
19
- (this as any).app.use(InteractionSchemasProvider);
20
-
21
- (this as any).app.pluginSettingsManager.add('skill-hub', {
22
- title: (this as any).t('Skill Hub'),
23
- icon: 'CodeOutlined',
24
- });
25
-
26
- (this as any).app.pluginSettingsManager.add('skill-hub.definitions', {
27
- title: (this as any).t('Skill Definitions'),
28
- Component: SkillManager,
29
- aclSnippet: 'pm.skill-hub',
30
- });
31
-
32
- (this as any).app.pluginSettingsManager.add('skill-hub.executions', {
33
- title: (this as any).t('Execution History'),
34
- Component: ExecutionHistory,
35
- aclSnippet: 'pm.skill-hub',
36
- });
37
-
38
- (this as any).app.pluginSettingsManager.add('skill-hub.metrics', {
39
- title: (this as any).t('Dashboard Metrics'),
40
- Component: SkillMetrics,
41
- aclSnippet: 'pm.skill-hub',
42
- });
43
-
44
-
45
-
46
- await this.registerSkillUiCards();
47
- }
48
-
49
- private async registerSkillUiCards() {
50
- const toolsManager = (this as any).app.aiManager?.toolsManager;
51
- if (!toolsManager) return;
52
-
53
- try {
54
- const { data } = await (this as any).app.apiClient.request({
55
- url: 'skillDefinitions:list',
56
- params: {
57
- filter: { enabled: true },
58
- fields: ['name', 'autoCall', 'interactionSchema'],
59
- pageSize: 200,
60
- },
61
- });
62
- const list = (data as any)?.data ?? [];
63
- for (const s of list) {
64
- if (s.autoCall) continue;
65
- if (!parseJsonText(s.interactionSchema, null)) continue;
66
- toolsManager.registerTools(`skill_hub_${sanitize(s.name)}`, { ui: { card: SkillHubCard } });
67
- }
68
- } catch {
69
- // user without ACL or backend unavailable — skip silently
70
- }
71
- }
72
- }
73
-
74
- export { SkillManager, ExecutionHistory, SkillMetrics };
75
- export default PluginSkillHubClient;
1
+ import { Plugin } from '@nocobase/client';
2
+ import { SkillManager } from './components/SkillManager';
3
+ import { ExecutionHistory } from './components/ExecutionHistory';
4
+ import { SkillMetrics } from './components/SkillMetrics';
5
+ import { LoopSettings } from './components/LoopSettings';
6
+ import { InteractionSchemasProvider } from './tools/InteractionSchemasProvider';
7
+ import { registerSkillLoopCards } from './tools/registerSkillLoopCards';
8
+
9
+ export class PluginSkillHubClient extends Plugin {
10
+ async load() {
11
+ (this as any).app.use(InteractionSchemasProvider);
12
+
13
+ (this as any).app.pluginSettingsManager.add('skill-hub', {
14
+ title: (this as any).t('Skill Hub'),
15
+ icon: 'CodeOutlined',
16
+ });
17
+
18
+ (this as any).app.pluginSettingsManager.add('skill-hub.definitions', {
19
+ title: (this as any).t('Skill Definitions'),
20
+ Component: SkillManager,
21
+ aclSnippet: 'pm.skill-hub',
22
+ });
23
+
24
+ (this as any).app.pluginSettingsManager.add('skill-hub.loop-settings', {
25
+ title: (this as any).t('Skill Review Settings'),
26
+ Component: LoopSettings,
27
+ aclSnippet: 'pm.skill-hub',
28
+ });
29
+
30
+ (this as any).app.pluginSettingsManager.add('skill-hub.executions', {
31
+ title: (this as any).t('Execution History'),
32
+ Component: ExecutionHistory,
33
+ aclSnippet: 'pm.skill-hub',
34
+ });
35
+
36
+ (this as any).app.pluginSettingsManager.add('skill-hub.metrics', {
37
+ title: (this as any).t('Dashboard Metrics'),
38
+ Component: SkillMetrics,
39
+ aclSnippet: 'pm.skill-hub',
40
+ });
41
+
42
+ await this.registerSkillUiCards();
43
+ }
44
+
45
+ private async registerSkillUiCards() {
46
+ await registerSkillLoopCards((this as any).app);
47
+ }
48
+ }
49
+
50
+ export { SkillManager, ExecutionHistory, SkillMetrics, LoopSettings };
51
+ export default PluginSkillHubClient;
@@ -1,13 +1,7 @@
1
1
  import React, { createContext, useContext, useEffect, useState } from 'react';
2
2
  import { useAPIClient } from '@nocobase/client';
3
3
  import { parseJsonText } from '../utils/jsonFields';
4
-
5
- export type InteractionSchema = {
6
- type: 'form' | 'select' | 'confirm';
7
- prompt: string;
8
- options?: { label: string; value: string | number }[];
9
- fields?: Record<string, { type?: string; title?: string; required?: boolean; enum?: any[] }>;
10
- };
4
+ import { InteractionSchema } from './loopTemplates';
11
5
 
12
6
  const Ctx = createContext<Map<string, InteractionSchema>>(new Map());
13
7
 
@@ -23,28 +17,74 @@ const sanitize = (name: string) =>
23
17
  export const InteractionSchemasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
24
18
  const api = useAPIClient();
25
19
  const [map, setMap] = useState<Map<string, InteractionSchema>>(new Map());
20
+ const [version, setVersion] = useState(0);
21
+
22
+ useEffect(() => {
23
+ const refresh = () => setVersion((current) => current + 1);
24
+ window.addEventListener('skill-hub-loop-settings-changed', refresh);
25
+ return () => window.removeEventListener('skill-hub-loop-settings-changed', refresh);
26
+ }, []);
26
27
 
27
28
  useEffect(() => {
28
29
  let cancelled = false;
29
- api
30
- .request({
30
+ const extractList = (data: any) => {
31
+ const value = data?.data?.data ?? data?.data ?? data ?? [];
32
+ return Array.isArray(value) ? value : [];
33
+ };
34
+ const schemaFromLoopConfig = (config: any): InteractionSchema | null => {
35
+ const schema = parseJsonText<InteractionSchema | null>(config.schema, null);
36
+ if (schema) {
37
+ return config.prompt && !schema.prompt ? { ...schema, prompt: config.prompt } : schema;
38
+ }
39
+ if (config.prompt) {
40
+ return {
41
+ type: 'confirm',
42
+ prompt: config.prompt,
43
+ };
44
+ }
45
+ return null;
46
+ };
47
+
48
+ Promise.all([
49
+ api.request({
31
50
  url: 'skillDefinitions:list',
32
51
  params: {
33
52
  filter: { enabled: true },
34
- fields: ['name', 'autoCall', 'interactionSchema'],
35
- pageSize: 200,
53
+ fields: ['id', 'name', 'autoCall', 'interactionSchema'],
54
+ pageSize: 500,
36
55
  },
37
- })
38
- .then(({ data }) => {
56
+ }),
57
+ api.request({
58
+ url: 'skillLoopConfigs:list',
59
+ params: {
60
+ filter: { enabled: true },
61
+ fields: ['skillId', 'enabled', 'schema', 'prompt', 'templateKey'],
62
+ pageSize: 500,
63
+ },
64
+ }).catch(() => ({ data: [] })),
65
+ ])
66
+ .then(([skillsResponse, loopConfigsResponse]) => {
39
67
  if (cancelled) return;
40
68
  const next = new Map<string, InteractionSchema>();
41
- const list = data?.data ?? [];
42
- for (const s of list) {
69
+ const skills = extractList(skillsResponse.data);
70
+ const loopConfigs = extractList(loopConfigsResponse.data);
71
+ const skillsById = new Map(skills.map((skill: any) => [String(skill.id), skill]));
72
+
73
+ for (const s of skills) {
43
74
  if (s.autoCall) continue;
44
75
  const schema = parseJsonText<InteractionSchema | null>(s.interactionSchema, null);
45
76
  if (!schema) continue;
46
77
  next.set(sanitize(s.name), schema);
47
78
  }
79
+
80
+ for (const config of loopConfigs) {
81
+ const skill = skillsById.get(String(config.skillId));
82
+ if (!skill?.name) continue;
83
+ const schema = schemaFromLoopConfig(config);
84
+ if (!schema) continue;
85
+ next.set(sanitize(skill.name), schema);
86
+ }
87
+
48
88
  setMap(next);
49
89
  })
50
90
  .catch(() => {
@@ -53,7 +93,7 @@ export const InteractionSchemasProvider: React.FC<{ children?: React.ReactNode }
53
93
  return () => {
54
94
  cancelled = true;
55
95
  };
56
- }, [api]);
96
+ }, [api, version]);
57
97
 
58
98
  return <Ctx.Provider value={map}>{children}</Ctx.Provider>;
59
99
  };
@@ -7,17 +7,48 @@ export const SkillHubCard: React.FC<ToolsUIProperties> = ({ toolCall, decisions
7
7
  const schemas = useInteractionSchemas();
8
8
  const [form] = Form.useForm();
9
9
 
10
- const skillKey = toolCall.name.replace(/^skill_hub_/, '');
10
+ const sanitize = (name: string) =>
11
+ name
12
+ .toLowerCase()
13
+ .replace(/[^a-z0-9_]/g, '_')
14
+ .replace(/_+/g, '_')
15
+ .replace(/^_|_$/g, '');
16
+ const isGenericExecutor = toolCall.name === 'skill_hub_execute';
17
+ const rawArgs = (toolCall.args as Record<string, any>) || {};
18
+ const skillKey = isGenericExecutor ? sanitize(rawArgs.skillName || '') : toolCall.name.replace(/^skill_hub_/, '');
11
19
  const schema = schemas.get(skillKey);
12
20
 
13
21
  const interrupted = toolCall.invokeStatus === 'init' || toolCall.invokeStatus === 'interrupted';
14
- if (!schema || !interrupted) {
22
+ if (!interrupted) {
15
23
  return null;
16
24
  }
17
25
 
26
+ if (!schema) {
27
+ return (
28
+ <Card size="small" style={{ marginTop: 8 }}>
29
+ <Typography.Paragraph style={{ marginBottom: 12 }}>
30
+ Review this Skill Hub tool call before execution.
31
+ </Typography.Paragraph>
32
+ <Space>
33
+ <Button type="primary" onClick={() => decisions.approve()}>
34
+ Run
35
+ </Button>
36
+ <Button onClick={() => decisions.reject('user_cancel')}>Cancel</Button>
37
+ </Space>
38
+ </Card>
39
+ );
40
+ }
41
+
18
42
  const onSubmit = async () => {
19
43
  const values = await form.validateFields();
20
- const args = schema.type === 'select' ? values : { ...(toolCall.args as any), ...values };
44
+ const args = isGenericExecutor
45
+ ? {
46
+ ...rawArgs,
47
+ input: schema.type === 'select' ? values : { ...(rawArgs.input || {}), ...values },
48
+ }
49
+ : schema.type === 'select'
50
+ ? values
51
+ : { ...rawArgs, ...values };
21
52
  await decisions.edit(args);
22
53
  };
23
54
 
@@ -39,7 +70,7 @@ export const SkillHubCard: React.FC<ToolsUIProperties> = ({ toolCall, decisions
39
70
  <Form
40
71
  form={form}
41
72
  layout="vertical"
42
- initialValues={(toolCall.args as Record<string, any>) || {}}
73
+ initialValues={(isGenericExecutor ? rawArgs.input : rawArgs) || {}}
43
74
  style={{ marginBottom: 8 }}
44
75
  >
45
76
  {schema.type === 'select' && (
@@ -0,0 +1,52 @@
1
+ export type InteractionSchema = {
2
+ type: 'form' | 'select' | 'confirm';
3
+ prompt: string;
4
+ options?: { label: string; value: string | number }[];
5
+ fields?: Record<string, { type?: string; title?: string; required?: boolean; enum?: any[] }>;
6
+ };
7
+
8
+ export type LoopTemplate = {
9
+ key: string;
10
+ title: string;
11
+ description: string;
12
+ schema: InteractionSchema;
13
+ };
14
+
15
+ export const LOOP_TEMPLATES: LoopTemplate[] = [
16
+ {
17
+ key: 'confirm',
18
+ title: 'Confirm before run',
19
+ description: 'Ask the user to approve or cancel the skill call.',
20
+ schema: {
21
+ type: 'confirm',
22
+ prompt: 'Review this skill call before execution.',
23
+ },
24
+ },
25
+ {
26
+ key: 'review-input',
27
+ title: 'Review editable input',
28
+ description: 'Show selected input fields so the user can edit them before running.',
29
+ schema: {
30
+ type: 'form',
31
+ prompt: 'Review and edit the skill input before execution.',
32
+ fields: {},
33
+ },
34
+ },
35
+ {
36
+ key: 'choose-option',
37
+ title: 'Choose one option',
38
+ description: 'Ask the user to choose one option before running.',
39
+ schema: {
40
+ type: 'select',
41
+ prompt: 'Choose how to run this skill.',
42
+ options: [
43
+ { label: 'Default', value: 'default' },
44
+ { label: 'Careful', value: 'careful' },
45
+ ],
46
+ },
47
+ },
48
+ ];
49
+
50
+ export function getLoopTemplate(key?: string) {
51
+ return LOOP_TEMPLATES.find((template) => template.key === key) || LOOP_TEMPLATES[0];
52
+ }