plugin-agent-orchestrator 1.0.20 → 1.0.21
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/hooks/useRunEventStream.d.ts +22 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -1
- package/dist/externalVersion.js +6 -6
- package/dist/server/collections/agent-execution-spans.js +24 -0
- package/dist/server/collections/agent-loop-runs.js +36 -0
- package/dist/server/collections/orchestrator-config.js +14 -0
- package/dist/server/migrations/20260601000000-add-token-fields.d.ts +7 -0
- package/dist/server/migrations/20260601000000-add-token-fields.js +101 -0
- package/dist/server/plugin.js +47 -0
- package/dist/server/resources/agent-loop.js +33 -25
- package/dist/server/resources/tracing.js +5 -8
- package/dist/server/services/AgentHarness.d.ts +2 -0
- package/dist/server/services/AgentHarness.js +56 -90
- package/dist/server/services/AgentLoopController.d.ts +33 -20
- package/dist/server/services/AgentLoopController.js +164 -125
- package/dist/server/services/AgentLoopRepository.js +16 -34
- package/dist/server/services/AgentLoopService.d.ts +28 -18
- package/dist/server/services/AgentLoopService.js +7 -1
- package/dist/server/services/AgentPlannerService.js +5 -25
- package/dist/server/services/AgentRegistryService.d.ts +8 -0
- package/dist/server/services/AgentRegistryService.js +34 -24
- package/dist/server/services/CircuitBreaker.d.ts +40 -0
- package/dist/server/services/CircuitBreaker.js +120 -0
- package/dist/server/services/ContextAggregator.d.ts +45 -0
- package/dist/server/services/ContextAggregator.js +201 -0
- package/dist/server/services/ExecutionSpanService.js +2 -5
- package/dist/server/services/RunEventBus.d.ts +9 -0
- package/dist/server/services/RunEventBus.js +73 -0
- package/dist/server/services/TokenTracker.d.ts +62 -0
- package/dist/server/services/TokenTracker.js +173 -0
- package/dist/server/tools/agent-loop.d.ts +8 -8
- package/dist/server/tools/agent-loop.js +30 -63
- package/dist/server/tools/delegate-task.js +14 -72
- package/dist/server/tools/orchestrator-plan.d.ts +6 -6
- package/dist/server/tools/orchestrator-plan.js +10 -47
- package/dist/server/types.d.ts +47 -0
- package/dist/server/types.js +24 -0
- package/dist/server/utils/ctx-utils.d.ts +30 -0
- package/dist/server/utils/ctx-utils.js +152 -0
- package/dist/server/utils/logging.d.ts +6 -0
- package/dist/server/utils/logging.js +86 -0
- package/package.json +44 -44
- package/src/client/AgentRunsTab.tsx +764 -764
- package/src/client/HarnessProfilesTab.tsx +247 -247
- package/src/client/OrchestratorSettings.tsx +106 -106
- package/src/client/RulesTab.tsx +716 -716
- package/src/client/hooks/useRunEventStream.ts +76 -0
- package/src/client/index.tsx +2 -1
- package/src/client/plugin.tsx +27 -27
- package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
- package/src/client/skill-hub/index.tsx +51 -51
- package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +99 -99
- package/src/client/skill-hub/tools/SkillHubCard.tsx +109 -109
- package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
- package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -58
- package/src/client/tools/PlanApprovalCard.tsx +175 -175
- package/src/client/tools/registerOrchestratorCards.ts +7 -7
- package/src/server/__tests__/agent-loop-controller.test.ts +375 -0
- package/src/server/__tests__/circuit-breaker.test.ts +169 -0
- package/src/server/__tests__/context-aggregator.test.ts +222 -0
- package/src/server/__tests__/parallel-execution.test.ts +318 -0
- package/src/server/__tests__/smoke.test.ts +120 -0
- package/src/server/collections/agent-execution-spans.ts +24 -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-runs.ts +38 -1
- package/src/server/collections/agent-loop-steps.ts +144 -144
- package/src/server/collections/orchestrator-config.ts +14 -0
- package/src/server/collections/skill-executions.ts +106 -106
- package/src/server/collections/skill-loop-configs.ts +65 -65
- 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 +142 -142
- package/src/server/migrations/20260601000000-add-token-fields.ts +89 -0
- package/src/server/plugin.ts +53 -0
- package/src/server/resources/agent-loop.ts +21 -12
- package/src/server/resources/tracing.ts +3 -7
- package/src/server/services/AgentHarness.ts +78 -116
- package/src/server/services/AgentLoopController.ts +197 -122
- package/src/server/services/AgentLoopRepository.ts +9 -25
- package/src/server/services/AgentLoopService.ts +13 -1
- package/src/server/services/AgentPlanValidator.ts +73 -73
- package/src/server/services/AgentPlannerService.ts +2 -25
- package/src/server/services/AgentRegistryService.ts +40 -31
- package/src/server/services/CircuitBreaker.ts +116 -0
- package/src/server/services/ContextAggregator.ts +239 -0
- package/src/server/services/ExecutionSpanService.ts +2 -4
- package/src/server/services/RunEventBus.ts +45 -0
- package/src/server/services/TokenTracker.ts +209 -0
- package/src/server/skill-hub/plugin.ts +898 -898
- package/src/server/skill-hub/tasks/SkillExecutionTask.ts +460 -460
- package/src/server/tools/agent-loop.ts +18 -57
- package/src/server/tools/delegate-task.ts +11 -93
- package/src/server/tools/orchestrator-plan.ts +26 -50
- package/src/server/tools/skill-execute.ts +160 -160
- package/src/server/types.ts +55 -0
- package/src/server/utils/ctx-utils.ts +118 -0
- package/src/server/utils/logging.ts +63 -0
|
@@ -1,51 +1,51 @@
|
|
|
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
|
+
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,99 +1,99 @@
|
|
|
1
|
-
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
2
|
-
import { useAPIClient } from '@nocobase/client';
|
|
3
|
-
import { parseJsonText } from '../utils/jsonFields';
|
|
4
|
-
import { InteractionSchema } from './loopTemplates';
|
|
5
|
-
|
|
6
|
-
const Ctx = createContext<Map<string, InteractionSchema>>(new Map());
|
|
7
|
-
|
|
8
|
-
export const useInteractionSchemas = () => useContext(Ctx);
|
|
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 const InteractionSchemasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
|
18
|
-
const api = useAPIClient();
|
|
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
|
-
}, []);
|
|
27
|
-
|
|
28
|
-
useEffect(() => {
|
|
29
|
-
let cancelled = false;
|
|
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({
|
|
50
|
-
url: 'skillDefinitions:list',
|
|
51
|
-
params: {
|
|
52
|
-
filter: { enabled: true },
|
|
53
|
-
fields: ['id', 'name', 'autoCall', 'interactionSchema'],
|
|
54
|
-
pageSize: 500,
|
|
55
|
-
},
|
|
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]) => {
|
|
67
|
-
if (cancelled) return;
|
|
68
|
-
const next = new Map<string, InteractionSchema>();
|
|
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) {
|
|
74
|
-
if (s.autoCall) continue;
|
|
75
|
-
const schema = parseJsonText<InteractionSchema | null>(s.interactionSchema, null);
|
|
76
|
-
if (!schema) continue;
|
|
77
|
-
next.set(sanitize(s.name), schema);
|
|
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
|
-
|
|
88
|
-
setMap(next);
|
|
89
|
-
})
|
|
90
|
-
.catch(() => {
|
|
91
|
-
// silently ignore — user may lack permission to list definitions
|
|
92
|
-
});
|
|
93
|
-
return () => {
|
|
94
|
-
cancelled = true;
|
|
95
|
-
};
|
|
96
|
-
}, [api, version]);
|
|
97
|
-
|
|
98
|
-
return <Ctx.Provider value={map}>{children}</Ctx.Provider>;
|
|
99
|
-
};
|
|
1
|
+
import React, { createContext, useContext, useEffect, useState } from 'react';
|
|
2
|
+
import { useAPIClient } from '@nocobase/client';
|
|
3
|
+
import { parseJsonText } from '../utils/jsonFields';
|
|
4
|
+
import { InteractionSchema } from './loopTemplates';
|
|
5
|
+
|
|
6
|
+
const Ctx = createContext<Map<string, InteractionSchema>>(new Map());
|
|
7
|
+
|
|
8
|
+
export const useInteractionSchemas = () => useContext(Ctx);
|
|
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 const InteractionSchemasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
|
18
|
+
const api = useAPIClient();
|
|
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
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
let cancelled = false;
|
|
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({
|
|
50
|
+
url: 'skillDefinitions:list',
|
|
51
|
+
params: {
|
|
52
|
+
filter: { enabled: true },
|
|
53
|
+
fields: ['id', 'name', 'autoCall', 'interactionSchema'],
|
|
54
|
+
pageSize: 500,
|
|
55
|
+
},
|
|
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]) => {
|
|
67
|
+
if (cancelled) return;
|
|
68
|
+
const next = new Map<string, InteractionSchema>();
|
|
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) {
|
|
74
|
+
if (s.autoCall) continue;
|
|
75
|
+
const schema = parseJsonText<InteractionSchema | null>(s.interactionSchema, null);
|
|
76
|
+
if (!schema) continue;
|
|
77
|
+
next.set(sanitize(s.name), schema);
|
|
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
|
+
|
|
88
|
+
setMap(next);
|
|
89
|
+
})
|
|
90
|
+
.catch(() => {
|
|
91
|
+
// silently ignore — user may lack permission to list definitions
|
|
92
|
+
});
|
|
93
|
+
return () => {
|
|
94
|
+
cancelled = true;
|
|
95
|
+
};
|
|
96
|
+
}, [api, version]);
|
|
97
|
+
|
|
98
|
+
return <Ctx.Provider value={map}>{children}</Ctx.Provider>;
|
|
99
|
+
};
|
|
@@ -1,109 +1,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
|
|
90
|
-
key={key}
|
|
91
|
-
name={key}
|
|
92
|
-
label={f.title || key}
|
|
93
|
-
rules={[{ required: !!f.required }]}
|
|
94
|
-
>
|
|
95
|
-
{renderField(key, f)}
|
|
96
|
-
</Form.Item>
|
|
97
|
-
))}
|
|
98
|
-
</Form>
|
|
99
|
-
)}
|
|
100
|
-
|
|
101
|
-
<Space>
|
|
102
|
-
<Button type="primary" onClick={schema.type === 'confirm' ? () => decisions.approve() : onSubmit}>
|
|
103
|
-
Run
|
|
104
|
-
</Button>
|
|
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
|
|
90
|
+
key={key}
|
|
91
|
+
name={key}
|
|
92
|
+
label={f.title || key}
|
|
93
|
+
rules={[{ required: !!f.required }]}
|
|
94
|
+
>
|
|
95
|
+
{renderField(key, f)}
|
|
96
|
+
</Form.Item>
|
|
97
|
+
))}
|
|
98
|
+
</Form>
|
|
99
|
+
)}
|
|
100
|
+
|
|
101
|
+
<Space>
|
|
102
|
+
<Button type="primary" onClick={schema.type === 'confirm' ? () => decisions.approve() : onSubmit}>
|
|
103
|
+
Run
|
|
104
|
+
</Button>
|
|
105
|
+
<Button onClick={() => decisions.reject('user_cancel')}>Cancel</Button>
|
|
106
|
+
</Space>
|
|
107
|
+
</Card>
|
|
108
|
+
);
|
|
109
|
+
};
|
|
@@ -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
|
+
}
|