plugin-agent-orchestrator 1.0.28 → 1.0.32
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 +9 -7
- package/dist/client/index.js +1 -1
- package/dist/client-v2/{214.723affb37c13bf7a.js → 214.79650a549273f163.js} +1 -1
- package/dist/client-v2/264.718a107e43fc163c.js +10 -0
- package/dist/client-v2/373.f5d5292e53c4e832.js +10 -0
- package/dist/client-v2/{41.1805b2edfaa4afe2.js → 41.ba6e080cc0488143.js} +1 -1
- package/dist/client-v2/418.29e713f79131eece.js +10 -0
- package/dist/client-v2/619.bd3c5698b40705c3.js +10 -0
- package/dist/client-v2/677.a991ce0250ff5c77.js +10 -0
- package/dist/client-v2/{70.a15d7fcec7c41768.js → 70.bda9518881c05360.js} +1 -1
- package/dist/client-v2/925.f5370de8f6632d65.js +10 -0
- package/dist/client-v2/index.js +1 -1
- package/dist/externalVersion.js +7 -10
- package/dist/locale/en-US.json +94 -25
- package/dist/locale/vi-VN.json +94 -25
- package/dist/locale/zh-CN.json +94 -25
- package/dist/server/collections/agent-execution-spans.js +37 -0
- package/dist/server/collections/agent-harness-profiles.js +2 -2
- package/dist/server/collections/agent-memory-contexts.js +125 -0
- package/dist/server/collections/orchestrator-logs.js +2 -2
- package/dist/server/migrations/20260425000000-add-interaction-schema.js +3 -1
- package/dist/server/migrations/20260427000000-change-packages-to-text.js +3 -1
- package/dist/server/migrations/20260427000001-change-other-json-to-text.js +6 -2
- package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.js +21 -19
- package/dist/server/migrations/20260621000000-native-policy-profile-defaults.js +193 -0
- package/dist/server/plugin.js +128 -74
- package/dist/server/resources/agent-monitor.js +454 -0
- package/dist/server/services/AgentHarness.js +24 -499
- package/dist/server/services/AgentMemoryContextService.js +216 -0
- package/dist/server/services/ExecutionSpanService.js +2 -2
- package/dist/server/services/NativeSubAgentObserver.js +413 -0
- package/dist/server/skill-hub/plugin.js +81 -5
- package/dist/server/skill-hub/tasks/SkillExecutionTask.js +9 -3
- package/dist/server/tools/delegate-task.js +11 -589
- package/dist/server/utils/skill-settings.js +18 -1
- package/package.json +47 -49
- package/src/client/AIEmployeesContext.tsx +5 -18
- package/src/client/AgentRunsTab.tsx +2 -771
- package/src/client/HarnessProfilesTab.tsx +2 -257
- package/src/client/OrchestratorSettings.tsx +97 -106
- package/src/client/RulesTab.tsx +2 -788
- package/src/client/plugin.tsx +0 -2
- package/src/client/skill-hub/components/ExecutionHistory.tsx +200 -202
- package/src/client/skill-hub/components/ExecutionProgress.tsx +51 -55
- package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
- package/src/client/skill-hub/components/SkillEditor.tsx +43 -39
- package/src/client/skill-hub/components/SkillManager.tsx +194 -181
- package/src/client/skill-hub/components/SkillTestPanel.tsx +141 -145
- package/src/client/skill-hub/locale.ts +16 -16
- package/src/client/skill-hub/tools/SkillHubCard.tsx +104 -109
- package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
- package/src/client/skill-hub/utils/jsonFields.ts +7 -3
- package/src/client-v2/components/AIEmployeesContext.tsx +3 -16
- package/src/client-v2/components/AgentRunsTab.tsx +182 -455
- package/src/client-v2/components/HarnessProfilesTab.tsx +34 -31
- package/src/client-v2/components/RulesTab.tsx +2 -782
- package/src/client-v2/components/TracingTab.tsx +1 -1
- package/src/client-v2/hooks/useApiRequest.ts +8 -1
- package/src/client-v2/pages/RulesPage.tsx +2 -2
- package/src/client-v2/plugin.tsx +3 -3
- package/src/locale/en-US.json +94 -25
- package/src/locale/vi-VN.json +94 -25
- package/src/locale/zh-CN.json +94 -25
- package/src/server/__tests__/native-sub-agent-observer.test.ts +246 -0
- package/src/server/__tests__/skill-settings.test.ts +6 -6
- package/src/server/__tests__/smoke.test.ts +1 -0
- package/src/server/collections/agent-execution-spans.ts +37 -0
- package/src/server/collections/agent-harness-profiles.ts +59 -59
- package/src/server/collections/agent-loop-events.ts +71 -71
- package/src/server/collections/agent-loop-steps.ts +144 -144
- package/src/server/collections/agent-memory-contexts.ts +95 -0
- package/src/server/collections/orchestrator-logs.ts +4 -4
- package/src/server/collections/skill-definitions.ts +111 -111
- package/src/server/collections/skill-executions.ts +106 -106
- package/src/server/collections/skill-loop-configs.ts +65 -65
- package/src/server/migrations/20260423000000-add-progress-fields.ts +14 -14
- package/src/server/migrations/20260425000000-add-interaction-schema.ts +3 -1
- package/src/server/migrations/20260427000000-change-packages-to-text.ts +4 -2
- package/src/server/migrations/20260427000001-change-other-json-to-text.ts +9 -5
- package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
- package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +145 -142
- package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +2 -2
- package/src/server/migrations/20260621000000-native-policy-profile-defaults.ts +193 -0
- package/src/server/plugin.ts +151 -94
- package/src/server/resources/agent-monitor.ts +482 -0
- package/src/server/services/AgentHarness.ts +38 -623
- package/src/server/services/AgentMemoryContextService.ts +256 -0
- package/src/server/services/AgentPlanValidator.ts +73 -73
- package/src/server/services/ExecutionSpanService.ts +6 -2
- package/src/server/services/FileManager.ts +144 -144
- package/src/server/services/NativeSubAgentObserver.ts +507 -0
- package/src/server/services/SkillManager.ts +583 -583
- package/src/server/services/SkillRepositoryService.ts +5 -7
- package/src/server/services/TokenTracker.ts +3 -3
- package/src/server/services/WorkerEnvManager.ts +1 -2
- package/src/server/skill-hub/actions/git-import.ts +5 -7
- package/src/server/skill-hub/plugin.ts +89 -6
- package/src/server/skill-hub/tasks/SkillExecutionTask.ts +470 -460
- package/src/server/skill-hub/utils/json-fields.ts +1 -1
- package/src/server/tools/delegate-task.ts +13 -847
- package/src/server/utils/skill-settings.ts +24 -6
- package/dist/client-v2/264.0533912e6c5ea2d7.js +0 -10
- package/dist/client-v2/418.5ae055abf141820e.js +0 -10
- package/dist/client-v2/619.d99d3c9e61c99064.js +0 -10
- package/dist/client-v2/892.72db4161511c8a16.js +0 -10
- package/dist/client-v2/926.87f660b670d85bcc.js +0 -10
- package/src/client/tools/PlanApprovalCard.tsx +0 -176
- package/src/client/tools/registerOrchestratorCards.ts +0 -17
|
@@ -1,145 +1,141 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import { Modal, Input, Button, Alert, Typography, Space, Spin } from 'antd';
|
|
3
|
-
import { Upload } from '@nocobase/client';
|
|
4
|
-
import { useApp } from '@nocobase/client-v2';
|
|
5
|
-
import { useT } from '../locale';
|
|
6
|
-
import { parseJsonText } from '../utils/jsonFields';
|
|
7
|
-
|
|
8
|
-
const { TextArea } = Input;
|
|
9
|
-
|
|
10
|
-
interface SkillTestPanelProps {
|
|
11
|
-
skill: any;
|
|
12
|
-
onClose: () => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export const SkillTestPanel: React.FC<SkillTestPanelProps> = ({ skill, onClose }) => {
|
|
16
|
-
const api = useApp().apiClient;
|
|
17
|
-
const t = useT();
|
|
18
|
-
const inputSchema = parseJsonText(skill.inputSchema, null);
|
|
19
|
-
const [input, setInput] = useState(
|
|
20
|
-
inputSchema?.properties
|
|
21
|
-
? JSON.stringify(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
</Space>
|
|
143
|
-
</Modal>
|
|
144
|
-
);
|
|
145
|
-
};
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Modal, Input, Button, Alert, Typography, Space, Spin } from 'antd';
|
|
3
|
+
import { Upload } from '@nocobase/client';
|
|
4
|
+
import { useApp } from '@nocobase/client-v2';
|
|
5
|
+
import { useT } from '../locale';
|
|
6
|
+
import { parseJsonText } from '../utils/jsonFields';
|
|
7
|
+
|
|
8
|
+
const { TextArea } = Input;
|
|
9
|
+
|
|
10
|
+
interface SkillTestPanelProps {
|
|
11
|
+
skill: any;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const SkillTestPanel: React.FC<SkillTestPanelProps> = ({ skill, onClose }) => {
|
|
16
|
+
const api = useApp().apiClient;
|
|
17
|
+
const t = useT();
|
|
18
|
+
const inputSchema = parseJsonText(skill.inputSchema, null);
|
|
19
|
+
const [input, setInput] = useState(
|
|
20
|
+
inputSchema?.properties
|
|
21
|
+
? JSON.stringify(Object.fromEntries(Object.keys(inputSchema.properties).map((k) => [k, ''])), null, 2)
|
|
22
|
+
: '{}',
|
|
23
|
+
);
|
|
24
|
+
const [running, setRunning] = useState(false);
|
|
25
|
+
const [result, setResult] = useState<any>(null);
|
|
26
|
+
const [error, setError] = useState('');
|
|
27
|
+
|
|
28
|
+
const handleRun = async () => {
|
|
29
|
+
setRunning(true);
|
|
30
|
+
setResult(null);
|
|
31
|
+
setError('');
|
|
32
|
+
|
|
33
|
+
let parsedInput;
|
|
34
|
+
try {
|
|
35
|
+
parsedInput = JSON.parse(input);
|
|
36
|
+
} catch {
|
|
37
|
+
setError(t('Invalid JSON input'));
|
|
38
|
+
setRunning(false);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const { data } = await api.request({
|
|
44
|
+
url: 'skillHub:test',
|
|
45
|
+
method: 'POST',
|
|
46
|
+
data: { skillId: skill.id, input: parsedInput },
|
|
47
|
+
});
|
|
48
|
+
const responseData = data?.data?.data || data?.data || data;
|
|
49
|
+
setResult(responseData);
|
|
50
|
+
} catch (err: any) {
|
|
51
|
+
setError(err?.response?.data?.errors?.[0]?.message || err.message || t('Execution failed'));
|
|
52
|
+
} finally {
|
|
53
|
+
setRunning(false);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<Modal
|
|
59
|
+
open
|
|
60
|
+
title={`${t('Test Skill')}: ${skill.title}`}
|
|
61
|
+
onCancel={onClose}
|
|
62
|
+
footer={[
|
|
63
|
+
<Button key="close" onClick={onClose}>
|
|
64
|
+
{t('Close')}
|
|
65
|
+
</Button>,
|
|
66
|
+
<Button key="run" type="primary" onClick={handleRun} loading={running}>
|
|
67
|
+
{t('Run')}
|
|
68
|
+
</Button>,
|
|
69
|
+
]}
|
|
70
|
+
width={640}
|
|
71
|
+
destroyOnClose
|
|
72
|
+
>
|
|
73
|
+
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
|
74
|
+
<div>
|
|
75
|
+
<Typography.Text strong>{t('Input (JSON)')}</Typography.Text>
|
|
76
|
+
<TextArea
|
|
77
|
+
rows={6}
|
|
78
|
+
value={input}
|
|
79
|
+
onChange={(e) => setInput(e.target.value)}
|
|
80
|
+
style={{ fontFamily: 'monospace', fontSize: 13, marginTop: 4 }}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{running && <Spin tip={t('Running skill on worker...')} />}
|
|
85
|
+
|
|
86
|
+
{error && <Alert type="error" message={error} showIcon />}
|
|
87
|
+
|
|
88
|
+
{result && (
|
|
89
|
+
<>
|
|
90
|
+
<Alert
|
|
91
|
+
type={result.status === 'succeeded' ? 'success' : 'error'}
|
|
92
|
+
message={result.status === 'succeeded' ? t('Succeeded') : t('Failed')}
|
|
93
|
+
description={`Duration: ${result.durationMs || 0}ms`}
|
|
94
|
+
showIcon
|
|
95
|
+
/>
|
|
96
|
+
|
|
97
|
+
{result.stdout && (
|
|
98
|
+
<div>
|
|
99
|
+
<Typography.Text strong>stdout:</Typography.Text>
|
|
100
|
+
<pre style={{ background: '#f5f5f5', padding: 8, maxHeight: 200, overflow: 'auto', fontSize: 12 }}>
|
|
101
|
+
{result.stdout}
|
|
102
|
+
</pre>
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
|
|
106
|
+
{result.stderr && (
|
|
107
|
+
<div>
|
|
108
|
+
<Typography.Text strong type="danger">
|
|
109
|
+
stderr:
|
|
110
|
+
</Typography.Text>
|
|
111
|
+
<pre style={{ background: '#fff2f0', padding: 8, maxHeight: 200, overflow: 'auto', fontSize: 12 }}>
|
|
112
|
+
{result.stderr}
|
|
113
|
+
</pre>
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
116
|
+
|
|
117
|
+
{result.files?.length > 0 && (
|
|
118
|
+
<div>
|
|
119
|
+
<Typography.Text strong>{t('Output Files')}:</Typography.Text>
|
|
120
|
+
<div style={{ marginTop: 8 }}>
|
|
121
|
+
<Upload.ReadPretty
|
|
122
|
+
value={result.files.map((f: any, i: number) => ({
|
|
123
|
+
id: `test-${f.name}-${i}`,
|
|
124
|
+
title: f.name,
|
|
125
|
+
filename: f.name,
|
|
126
|
+
extname: f.name.includes('.') ? `.${f.name.split('.').pop()}` : '',
|
|
127
|
+
url: f.downloadUrl,
|
|
128
|
+
status: 'done',
|
|
129
|
+
}))}
|
|
130
|
+
multiple={true}
|
|
131
|
+
showFileName={true}
|
|
132
|
+
/>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
)}
|
|
136
|
+
</>
|
|
137
|
+
)}
|
|
138
|
+
</Space>
|
|
139
|
+
</Modal>
|
|
140
|
+
);
|
|
141
|
+
};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { useApp } from '@nocobase/client-v2';
|
|
2
|
-
import { useCallback } from 'react';
|
|
3
|
-
export const namespace = 'plugin-agent-orchestrator';
|
|
4
|
-
|
|
5
|
-
export function useT() {
|
|
6
|
-
const app = useApp();
|
|
7
|
-
return useCallback(
|
|
8
|
-
(str: string, options?: any): string =>
|
|
9
|
-
app.i18n.t(str, { ns: [namespace, 'client'], ...options }) as unknown as string,
|
|
10
|
-
[app.i18n]
|
|
11
|
-
);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function tStr(key: string) {
|
|
15
|
-
return `{{t(${JSON.stringify(key)}, { ns: ['${namespace}', 'client'], nsMode: 'fallback' })}}`;
|
|
16
|
-
}
|
|
1
|
+
import { useApp } from '@nocobase/client-v2';
|
|
2
|
+
import { useCallback } from 'react';
|
|
3
|
+
export const namespace = 'plugin-agent-orchestrator';
|
|
4
|
+
|
|
5
|
+
export function useT() {
|
|
6
|
+
const app = useApp();
|
|
7
|
+
return useCallback(
|
|
8
|
+
(str: string, options?: any): string =>
|
|
9
|
+
app.i18n.t(str, { ns: [namespace, 'client'], ...options }) as unknown as string,
|
|
10
|
+
[app.i18n],
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function tStr(key: string) {
|
|
15
|
+
return `{{t(${JSON.stringify(key)}, { ns: ['${namespace}', 'client'], nsMode: 'fallback' })}}`;
|
|
16
|
+
}
|
|
@@ -1,109 +1,104 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Form, Input, Select, Radio, InputNumber, Button, Space, Card, Typography } from 'antd';
|
|
3
|
-
import { ToolsUIProperties } from '@nocobase/client';
|
|
4
|
-
import { useInteractionSchemas } from './InteractionSchemasProvider';
|
|
5
|
-
|
|
6
|
-
export const SkillHubCard: React.FC<ToolsUIProperties> = ({ toolCall, decisions }) => {
|
|
7
|
-
const schemas = useInteractionSchemas();
|
|
8
|
-
const [form] = Form.useForm();
|
|
9
|
-
|
|
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_/, '');
|
|
19
|
-
const schema = schemas.get(skillKey);
|
|
20
|
-
|
|
21
|
-
const interrupted = toolCall.invokeStatus === 'init' || toolCall.invokeStatus === 'interrupted';
|
|
22
|
-
if (!interrupted) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
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
|
-
|
|
42
|
-
const onSubmit = async () => {
|
|
43
|
-
const values = await form.validateFields();
|
|
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 };
|
|
52
|
-
await decisions.edit(args);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const renderField = (key: string, f: any) => {
|
|
56
|
-
if (f?.enum) {
|
|
57
|
-
return <Select options={f.enum.map((v: any) => ({ value: v, label: String(v) }))} />;
|
|
58
|
-
}
|
|
59
|
-
if (f?.type === 'number' || f?.type === 'integer') {
|
|
60
|
-
return <InputNumber style={{ width: '100%' }} />;
|
|
61
|
-
}
|
|
62
|
-
return <Input />;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
return (
|
|
66
|
-
<Card size="small" style={{ marginTop: 8 }}>
|
|
67
|
-
<Typography.Paragraph style={{ marginBottom: 12 }}>{schema.prompt}</Typography.Paragraph>
|
|
68
|
-
|
|
69
|
-
{schema.type !== 'confirm' && (
|
|
70
|
-
<Form
|
|
71
|
-
form={form}
|
|
72
|
-
layout="vertical"
|
|
73
|
-
initialValues={(isGenericExecutor ? rawArgs.input : rawArgs) || {}}
|
|
74
|
-
style={{ marginBottom: 8 }}
|
|
75
|
-
>
|
|
76
|
-
{schema.type === 'select' && (
|
|
77
|
-
<Form.Item name="choice" rules={[{ required: true }]}>
|
|
78
|
-
<Radio.Group>
|
|
79
|
-
{(schema.options ?? []).map((o) => (
|
|
80
|
-
<Radio key={String(o.value)} value={o.value}>
|
|
81
|
-
{o.label}
|
|
82
|
-
</Radio>
|
|
83
|
-
))}
|
|
84
|
-
</Radio.Group>
|
|
85
|
-
</Form.Item>
|
|
86
|
-
)}
|
|
87
|
-
{schema.type === 'form' &&
|
|
88
|
-
Object.entries(schema.fields ?? {}).map(([key, f]) => (
|
|
89
|
-
<Form.Item
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
<Button onClick={() => decisions.reject('user_cancel')}>Cancel</Button>
|
|
106
|
-
</Space>
|
|
107
|
-
</Card>
|
|
108
|
-
);
|
|
109
|
-
};
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Form, Input, Select, Radio, InputNumber, Button, Space, Card, Typography } from 'antd';
|
|
3
|
+
import { ToolsUIProperties } from '@nocobase/client';
|
|
4
|
+
import { useInteractionSchemas } from './InteractionSchemasProvider';
|
|
5
|
+
|
|
6
|
+
export const SkillHubCard: React.FC<ToolsUIProperties> = ({ toolCall, decisions }) => {
|
|
7
|
+
const schemas = useInteractionSchemas();
|
|
8
|
+
const [form] = Form.useForm();
|
|
9
|
+
|
|
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_/, '');
|
|
19
|
+
const schema = schemas.get(skillKey);
|
|
20
|
+
|
|
21
|
+
const interrupted = toolCall.invokeStatus === 'init' || toolCall.invokeStatus === 'interrupted';
|
|
22
|
+
if (!interrupted) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
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
|
+
|
|
42
|
+
const onSubmit = async () => {
|
|
43
|
+
const values = await form.validateFields();
|
|
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 };
|
|
52
|
+
await decisions.edit(args);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const renderField = (key: string, f: any) => {
|
|
56
|
+
if (f?.enum) {
|
|
57
|
+
return <Select options={f.enum.map((v: any) => ({ value: v, label: String(v) }))} />;
|
|
58
|
+
}
|
|
59
|
+
if (f?.type === 'number' || f?.type === 'integer') {
|
|
60
|
+
return <InputNumber style={{ width: '100%' }} />;
|
|
61
|
+
}
|
|
62
|
+
return <Input />;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Card size="small" style={{ marginTop: 8 }}>
|
|
67
|
+
<Typography.Paragraph style={{ marginBottom: 12 }}>{schema.prompt}</Typography.Paragraph>
|
|
68
|
+
|
|
69
|
+
{schema.type !== 'confirm' && (
|
|
70
|
+
<Form
|
|
71
|
+
form={form}
|
|
72
|
+
layout="vertical"
|
|
73
|
+
initialValues={(isGenericExecutor ? rawArgs.input : rawArgs) || {}}
|
|
74
|
+
style={{ marginBottom: 8 }}
|
|
75
|
+
>
|
|
76
|
+
{schema.type === 'select' && (
|
|
77
|
+
<Form.Item name="choice" rules={[{ required: true }]}>
|
|
78
|
+
<Radio.Group>
|
|
79
|
+
{(schema.options ?? []).map((o) => (
|
|
80
|
+
<Radio key={String(o.value)} value={o.value}>
|
|
81
|
+
{o.label}
|
|
82
|
+
</Radio>
|
|
83
|
+
))}
|
|
84
|
+
</Radio.Group>
|
|
85
|
+
</Form.Item>
|
|
86
|
+
)}
|
|
87
|
+
{schema.type === 'form' &&
|
|
88
|
+
Object.entries(schema.fields ?? {}).map(([key, f]) => (
|
|
89
|
+
<Form.Item key={key} name={key} label={f.title || key} rules={[{ required: !!f.required }]}>
|
|
90
|
+
{renderField(key, f)}
|
|
91
|
+
</Form.Item>
|
|
92
|
+
))}
|
|
93
|
+
</Form>
|
|
94
|
+
)}
|
|
95
|
+
|
|
96
|
+
<Space>
|
|
97
|
+
<Button type="primary" onClick={schema.type === 'confirm' ? () => decisions.approve() : onSubmit}>
|
|
98
|
+
Run
|
|
99
|
+
</Button>
|
|
100
|
+
<Button onClick={() => decisions.reject('user_cancel')}>Cancel</Button>
|
|
101
|
+
</Space>
|
|
102
|
+
</Card>
|
|
103
|
+
);
|
|
104
|
+
};
|
|
@@ -1,52 +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
|
-
}
|
|
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
|
+
}
|