plugin-agent-orchestrator 1.0.22 → 1.0.23
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/client-v2.d.ts +2 -0
- package/client-v2.js +1 -0
- package/dist/client/index.js +1 -1
- package/dist/client-v2/214.723affb37c13bf7a.js +10 -0
- package/dist/client-v2/264.0533912e6c5ea2d7.js +10 -0
- package/dist/client-v2/41.1805b2edfaa4afe2.js +10 -0
- package/dist/client-v2/418.5ae055abf141820e.js +10 -0
- package/dist/client-v2/619.d99d3c9e61c99064.js +10 -0
- package/dist/client-v2/70.a15d7fcec7c41768.js +10 -0
- package/dist/client-v2/892.72db4161511c8a16.js +10 -0
- package/dist/client-v2/926.87f660b670d85bcc.js +10 -0
- package/dist/client-v2/index.js +10 -0
- package/dist/externalVersion.js +7 -6
- package/dist/locale/en-US.json +7 -0
- package/dist/locale/vi-VN.json +7 -0
- package/dist/locale/zh-CN.json +27 -0
- package/dist/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.js +63 -0
- package/dist/server/plugin.js +32 -1
- package/dist/server/services/AgentHarness.js +52 -27
- package/dist/server/services/AgentLoopController.js +8 -2
- package/dist/server/services/AgentLoopService.js +1 -1
- package/dist/server/services/AgentRegistryService.js +53 -42
- package/dist/server/services/CircuitBreaker.js +7 -2
- package/dist/server/services/CodeValidator.js +48 -14
- package/dist/server/services/SandboxRunner.js +18 -14
- package/dist/server/skill-hub/plugin.js +44 -17
- package/dist/server/tools/delegate-task.js +7 -2
- package/dist/server/tools/skill-execute.js +33 -2
- package/dist/server/utils/ai-manager.js +51 -0
- package/dist/server/utils/ctx-utils.js +11 -0
- package/dist/server/utils/skill-settings.js +122 -0
- package/package.json +49 -45
- package/src/client/AIEmployeesContext.tsx +51 -14
- package/src/client/AgentRunsTab.tsx +767 -764
- package/src/client/HarnessProfilesTab.tsx +254 -247
- package/src/client/RulesTab.tsx +780 -716
- package/src/client/TracingTab.tsx +1 -0
- package/src/client/plugin.tsx +34 -27
- package/src/client/skill-hub/components/GitSkillImport.tsx +10 -3
- package/src/client/skill-hub/components/SkillMetrics.tsx +157 -124
- package/src/client/skill-hub/index.tsx +58 -51
- package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +132 -99
- package/src/client/skill-hub/tools/registerSkillLoopCards.ts +71 -58
- package/src/client/tools/registerOrchestratorCards.ts +17 -7
- package/src/client-v2/components/AIEmployeeSelect.tsx +47 -0
- package/src/client-v2/components/AIEmployeesContext.tsx +110 -0
- package/src/client-v2/components/AgentRunsTab.tsx +767 -0
- package/src/client-v2/components/HarnessProfilesTab.tsx +254 -0
- package/src/client-v2/components/RulesTab.tsx +782 -0
- package/src/client-v2/components/TracingTab.tsx +432 -0
- package/src/client-v2/hooks/useApiRequest.ts +114 -0
- package/src/client-v2/index.tsx +1 -0
- package/src/client-v2/pages/AgentRunsPage.tsx +13 -0
- package/src/client-v2/pages/ExecutionHistoryPage.tsx +10 -0
- package/src/client-v2/pages/HarnessProfilesPage.tsx +10 -0
- package/src/client-v2/pages/LoopSettingsPage.tsx +10 -0
- package/src/client-v2/pages/RulesPage.tsx +13 -0
- package/src/client-v2/pages/SkillDefinitionsPage.tsx +10 -0
- package/src/client-v2/pages/SkillMetricsPage.tsx +10 -0
- package/src/client-v2/pages/TracingPage.tsx +13 -0
- package/src/client-v2/plugin.tsx +70 -0
- package/src/client-v2/skill-hub/components/ExecutionHistory.tsx +196 -0
- package/src/client-v2/skill-hub/components/FileLinkList.tsx +37 -0
- package/src/client-v2/skill-hub/components/GitSkillImport.tsx +539 -0
- package/src/client-v2/skill-hub/components/LoopSettings.tsx +331 -0
- package/src/client-v2/skill-hub/components/SkillEditor.tsx +453 -0
- package/src/client-v2/skill-hub/components/SkillManager.tsx +174 -0
- package/src/client-v2/skill-hub/components/SkillMetrics.tsx +157 -0
- package/src/client-v2/skill-hub/components/SkillTestPanel.tsx +135 -0
- package/src/client-v2/skill-hub/locale.ts +13 -0
- package/src/client-v2/skill-hub/tools/loopTemplates.ts +52 -0
- package/src/client-v2/skill-hub/utils/jsonFields.ts +41 -0
- package/src/client-v2/utils/jsonFields.ts +41 -0
- package/src/locale/en-US.json +7 -0
- package/src/locale/vi-VN.json +7 -0
- package/src/locale/zh-CN.json +27 -0
- package/src/server/__tests__/agent-registry-service.test.ts +147 -0
- package/src/server/__tests__/code-validator.test.ts +63 -0
- package/src/server/__tests__/skill-execute.test.ts +33 -0
- package/src/server/__tests__/skill-settings.test.ts +63 -0
- package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +39 -0
- package/src/server/plugin.ts +62 -21
- package/src/server/services/AgentHarness.ts +49 -22
- package/src/server/services/AgentLoopController.ts +17 -6
- package/src/server/services/AgentLoopService.ts +1 -1
- package/src/server/services/AgentPlannerService.ts +10 -0
- package/src/server/services/AgentRegistryService.ts +89 -47
- package/src/server/services/CircuitBreaker.ts +10 -0
- package/src/server/services/CodeValidator.ts +237 -159
- package/src/server/services/SandboxRunner.ts +203 -189
- package/src/server/skill-hub/plugin.ts +933 -898
- package/src/server/tools/delegate-task.ts +12 -9
- package/src/server/tools/skill-execute.ts +194 -160
- package/src/server/utils/ai-manager.ts +24 -0
- package/src/server/utils/ctx-utils.ts +14 -0
- package/src/server/utils/skill-settings.ts +116 -0
|
@@ -1,99 +1,132 @@
|
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
+
type ApiClientWithAuth = ReturnType<typeof useAPIClient> & {
|
|
11
|
+
auth?: {
|
|
12
|
+
getToken?: () => string | null | undefined;
|
|
13
|
+
token?: string | null;
|
|
14
|
+
};
|
|
15
|
+
app?: {
|
|
16
|
+
eventBus?: {
|
|
17
|
+
addEventListener?: (type: string, listener: EventListenerOrEventListenerObject) => void;
|
|
18
|
+
removeEventListener?: (type: string, listener: EventListenerOrEventListenerObject) => void;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const sanitize = (name: string) =>
|
|
24
|
+
name
|
|
25
|
+
.toLowerCase()
|
|
26
|
+
.replace(/[^a-z0-9_]/g, '_')
|
|
27
|
+
.replace(/_+/g, '_')
|
|
28
|
+
.replace(/^_|_$/g, '');
|
|
29
|
+
|
|
30
|
+
export const InteractionSchemasProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
|
31
|
+
const api = useAPIClient() as ApiClientWithAuth;
|
|
32
|
+
const authToken = api.auth?.getToken?.() || api.auth?.token || '';
|
|
33
|
+
const [map, setMap] = useState<Map<string, InteractionSchema>>(new Map());
|
|
34
|
+
const [version, setVersion] = useState(0);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const refresh = () => setVersion((current) => current + 1);
|
|
38
|
+
const refreshAfterSignIn = (event: Event) => {
|
|
39
|
+
const token = (event as CustomEvent<{ token?: string | null }>).detail?.token;
|
|
40
|
+
if (token) {
|
|
41
|
+
refresh();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
window.addEventListener('skill-hub-loop-settings-changed', refresh);
|
|
45
|
+
api.app?.eventBus?.addEventListener?.('auth:tokenChanged', refreshAfterSignIn);
|
|
46
|
+
return () => {
|
|
47
|
+
window.removeEventListener('skill-hub-loop-settings-changed', refresh);
|
|
48
|
+
api.app?.eventBus?.removeEventListener?.('auth:tokenChanged', refreshAfterSignIn);
|
|
49
|
+
};
|
|
50
|
+
}, [api]);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
let cancelled = false;
|
|
54
|
+
if (!authToken) {
|
|
55
|
+
return () => {
|
|
56
|
+
cancelled = true;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const extractList = (data: any) => {
|
|
60
|
+
const value = data?.data?.data ?? data?.data ?? data ?? [];
|
|
61
|
+
return Array.isArray(value) ? value : [];
|
|
62
|
+
};
|
|
63
|
+
const schemaFromLoopConfig = (config: any): InteractionSchema | null => {
|
|
64
|
+
const schema = parseJsonText<InteractionSchema | null>(config.schema, null);
|
|
65
|
+
if (schema) {
|
|
66
|
+
return config.prompt && !schema.prompt ? { ...schema, prompt: config.prompt } : schema;
|
|
67
|
+
}
|
|
68
|
+
if (config.prompt) {
|
|
69
|
+
return {
|
|
70
|
+
type: 'confirm',
|
|
71
|
+
prompt: config.prompt,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
Promise.all([
|
|
78
|
+
api.request({
|
|
79
|
+
url: 'skillDefinitions:list',
|
|
80
|
+
skipNotify: true,
|
|
81
|
+
params: {
|
|
82
|
+
filter: { enabled: true },
|
|
83
|
+
fields: ['id', 'name', 'autoCall', 'interactionSchema'],
|
|
84
|
+
pageSize: 500,
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
api
|
|
88
|
+
.request({
|
|
89
|
+
url: 'skillLoopConfigs:list',
|
|
90
|
+
skipNotify: true,
|
|
91
|
+
params: {
|
|
92
|
+
filter: { enabled: true },
|
|
93
|
+
fields: ['skillId', 'enabled', 'schema', 'prompt', 'templateKey'],
|
|
94
|
+
pageSize: 500,
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
.catch(() => ({ data: [] })),
|
|
98
|
+
])
|
|
99
|
+
.then(([skillsResponse, loopConfigsResponse]) => {
|
|
100
|
+
if (cancelled) return;
|
|
101
|
+
const next = new Map<string, InteractionSchema>();
|
|
102
|
+
const skills = extractList(skillsResponse.data);
|
|
103
|
+
const loopConfigs = extractList(loopConfigsResponse.data);
|
|
104
|
+
const skillsById = new Map(skills.map((skill: any) => [String(skill.id), skill]));
|
|
105
|
+
|
|
106
|
+
for (const s of skills) {
|
|
107
|
+
if (s.autoCall) continue;
|
|
108
|
+
const schema = parseJsonText<InteractionSchema | null>(s.interactionSchema, null);
|
|
109
|
+
if (!schema) continue;
|
|
110
|
+
next.set(sanitize(s.name), schema);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const config of loopConfigs) {
|
|
114
|
+
const skill = skillsById.get(String(config.skillId));
|
|
115
|
+
if (!skill?.name) continue;
|
|
116
|
+
const schema = schemaFromLoopConfig(config);
|
|
117
|
+
if (!schema) continue;
|
|
118
|
+
next.set(sanitize(skill.name), schema);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
setMap(next);
|
|
122
|
+
})
|
|
123
|
+
.catch(() => {
|
|
124
|
+
// silently ignore — user may lack permission to list definitions
|
|
125
|
+
});
|
|
126
|
+
return () => {
|
|
127
|
+
cancelled = true;
|
|
128
|
+
};
|
|
129
|
+
}, [api, authToken, version]);
|
|
130
|
+
|
|
131
|
+
return <Ctx.Provider value={map}>{children}</Ctx.Provider>;
|
|
132
|
+
};
|
|
@@ -1,58 +1,71 @@
|
|
|
1
|
-
import { SkillHubCard } from './SkillHubCard';
|
|
2
|
-
import { parseJsonText } from '../utils/jsonFields';
|
|
3
|
-
|
|
4
|
-
const sanitize = (name: string) =>
|
|
5
|
-
name
|
|
6
|
-
.toLowerCase()
|
|
7
|
-
.replace(/[^a-z0-9_]/g, '_')
|
|
8
|
-
.replace(/_+/g, '_')
|
|
9
|
-
.replace(/^_|_$/g, '');
|
|
10
|
-
|
|
11
|
-
const extractList = (data: any) => {
|
|
12
|
-
const value = data?.data?.data ?? data?.data ?? data ?? [];
|
|
13
|
-
return Array.isArray(value) ? value : [];
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
1
|
+
import { SkillHubCard } from './SkillHubCard';
|
|
2
|
+
import { parseJsonText } from '../utils/jsonFields';
|
|
3
|
+
|
|
4
|
+
const sanitize = (name: string) =>
|
|
5
|
+
name
|
|
6
|
+
.toLowerCase()
|
|
7
|
+
.replace(/[^a-z0-9_]/g, '_')
|
|
8
|
+
.replace(/_+/g, '_')
|
|
9
|
+
.replace(/^_|_$/g, '');
|
|
10
|
+
|
|
11
|
+
const extractList = (data: any) => {
|
|
12
|
+
const value = data?.data?.data ?? data?.data ?? data ?? [];
|
|
13
|
+
return Array.isArray(value) ? value : [];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const getAuthToken = (app: any) => app?.apiClient?.auth?.getToken?.() || app?.apiClient?.auth?.token || '';
|
|
17
|
+
|
|
18
|
+
const registerSkillHubCard = (toolsManager: any, name: string) => {
|
|
19
|
+
try {
|
|
20
|
+
toolsManager.registerTools(name, { ui: { card: SkillHubCard } });
|
|
21
|
+
} catch (err: unknown) {
|
|
22
|
+
if (!(err instanceof Error) || !err.message.includes('override existing keys')) {
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export async function registerSkillLoopCards(app: any) {
|
|
29
|
+
const toolsManager = app.aiManager?.toolsManager;
|
|
30
|
+
if (!toolsManager) return;
|
|
31
|
+
registerSkillHubCard(toolsManager, 'skill_hub_execute');
|
|
32
|
+
if (!getAuthToken(app)) return;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const skillsResponse = await app.apiClient.request({
|
|
36
|
+
url: 'skillDefinitions:list',
|
|
37
|
+
skipNotify: true,
|
|
38
|
+
params: {
|
|
39
|
+
filter: { enabled: true },
|
|
40
|
+
fields: ['id', 'name', 'autoCall', 'interactionSchema'],
|
|
41
|
+
pageSize: 500,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
let loopSkillIds = new Set<string>();
|
|
46
|
+
try {
|
|
47
|
+
const loopConfigsResponse = await app.apiClient.request({
|
|
48
|
+
url: 'skillLoopConfigs:list',
|
|
49
|
+
skipNotify: true,
|
|
50
|
+
params: {
|
|
51
|
+
filter: { enabled: true },
|
|
52
|
+
fields: ['skillId'],
|
|
53
|
+
pageSize: 500,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
loopSkillIds = new Set(extractList(loopConfigsResponse.data).map((config: any) => String(config.skillId)));
|
|
57
|
+
} catch {
|
|
58
|
+
// Older deployments may not have the collection before migration/sync.
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const skills = extractList(skillsResponse.data);
|
|
62
|
+
for (const skill of skills) {
|
|
63
|
+
const hasLoopConfig = loopSkillIds.has(String(skill.id));
|
|
64
|
+
const hasLegacySchema = !skill.autoCall && !!parseJsonText(skill.interactionSchema, null);
|
|
65
|
+
if (!hasLoopConfig && !hasLegacySchema) continue;
|
|
66
|
+
registerSkillHubCard(toolsManager, `skill_hub_${sanitize(skill.name)}`);
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// user without ACL or backend unavailable - skip silently
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
import { PlanApprovalCard } from './PlanApprovalCard';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { PlanApprovalCard } from './PlanApprovalCard';
|
|
2
|
+
|
|
3
|
+
const registerToolCard = (toolsManager: any, name: string) => {
|
|
4
|
+
try {
|
|
5
|
+
toolsManager.registerTools(name, { ui: { card: PlanApprovalCard } });
|
|
6
|
+
} catch (err: unknown) {
|
|
7
|
+
if (!(err instanceof Error) || !err.message.includes('override existing keys')) {
|
|
8
|
+
throw err;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export async function registerOrchestratorCards(app: any) {
|
|
14
|
+
const toolsManager = app.aiManager?.toolsManager;
|
|
15
|
+
if (!toolsManager) return;
|
|
16
|
+
registerToolCard(toolsManager, 'orchestrator_execute_plan');
|
|
17
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Select } from 'antd';
|
|
3
|
+
import { useAIEmployees } from './AIEmployeesContext';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reusable Select component for AI Employees.
|
|
7
|
+
* Uses shared AIEmployeesContext instead of making its own API call.
|
|
8
|
+
*/
|
|
9
|
+
export const AIEmployeeSelect: React.FC<{
|
|
10
|
+
value?: string;
|
|
11
|
+
onChange?: (value: string) => void;
|
|
12
|
+
exclude?: string; // username to exclude (prevent self-reference)
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
}> = ({ value, onChange, exclude, placeholder = 'Select AI Employee...' }) => {
|
|
15
|
+
const { employees, loading } = useAIEmployees();
|
|
16
|
+
|
|
17
|
+
const options = React.useMemo(() => {
|
|
18
|
+
return employees
|
|
19
|
+
.filter((emp) => !exclude || emp.username !== exclude)
|
|
20
|
+
.map((emp) => ({
|
|
21
|
+
label: emp.nickname,
|
|
22
|
+
value: emp.username,
|
|
23
|
+
description: emp.about,
|
|
24
|
+
}));
|
|
25
|
+
}, [employees, exclude]);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<Select
|
|
29
|
+
loading={loading}
|
|
30
|
+
options={options}
|
|
31
|
+
value={value}
|
|
32
|
+
onChange={onChange}
|
|
33
|
+
placeholder={placeholder}
|
|
34
|
+
showSearch
|
|
35
|
+
filterOption={(input, option) =>
|
|
36
|
+
(option?.label ?? '').toString().toLowerCase().includes(input.toLowerCase()) ||
|
|
37
|
+
(option?.value ?? '').toString().toLowerCase().includes(input.toLowerCase())
|
|
38
|
+
}
|
|
39
|
+
optionRender={(option) => (
|
|
40
|
+
<div>
|
|
41
|
+
<div style={{ fontWeight: 500 }}>{option.label}</div>
|
|
42
|
+
{option.data.description && <div style={{ fontSize: 12, color: '#888' }}>{option.data.description}</div>}
|
|
43
|
+
</div>
|
|
44
|
+
)}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React, { createContext, useContext } from 'react';
|
|
2
|
+
import { useRequest } from '../hooks/useApiRequest';
|
|
3
|
+
|
|
4
|
+
interface AIEmployeeInfo {
|
|
5
|
+
username: string;
|
|
6
|
+
nickname: string;
|
|
7
|
+
about?: string;
|
|
8
|
+
tools: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface AIEmployeesContextType {
|
|
12
|
+
employees: AIEmployeeInfo[];
|
|
13
|
+
employeeMap: Map<string, string>;
|
|
14
|
+
toolNamesMap: Map<string, Set<string>>;
|
|
15
|
+
loading: boolean;
|
|
16
|
+
refresh: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const AIEmployeesContext = createContext<AIEmployeesContextType>({
|
|
20
|
+
employees: [],
|
|
21
|
+
employeeMap: new Map(),
|
|
22
|
+
toolNamesMap: new Map(),
|
|
23
|
+
loading: false,
|
|
24
|
+
refresh: () => {},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const orchestratorToolNames = new Set([
|
|
28
|
+
'orchestrator_plan_goal',
|
|
29
|
+
'orchestrator_execute_plan',
|
|
30
|
+
'orchestrator_status',
|
|
31
|
+
'orchestrator_cancel',
|
|
32
|
+
'external_rag_search',
|
|
33
|
+
'skill_hub_execute',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
function isToolLikeName(name: string) {
|
|
37
|
+
return (
|
|
38
|
+
orchestratorToolNames.has(name) ||
|
|
39
|
+
name.startsWith('delegate_') ||
|
|
40
|
+
name.startsWith('dispatch_subagents_') ||
|
|
41
|
+
name.startsWith('skill_hub_') ||
|
|
42
|
+
name.startsWith('browser_') ||
|
|
43
|
+
name.startsWith('drawio-')
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function extractToolNames(skillSettings: any) {
|
|
48
|
+
const tools = Array.isArray(skillSettings?.tools)
|
|
49
|
+
? skillSettings.tools
|
|
50
|
+
.map((tool: any) => (typeof tool === 'string' ? tool : tool?.name))
|
|
51
|
+
.filter((name: any): name is string => typeof name === 'string' && name.length > 0)
|
|
52
|
+
: [];
|
|
53
|
+
|
|
54
|
+
const legacyTools = Array.isArray(skillSettings?.skills)
|
|
55
|
+
? skillSettings.skills
|
|
56
|
+
.map((skill: any) => {
|
|
57
|
+
if (typeof skill === 'string') {
|
|
58
|
+
return isToolLikeName(skill) ? skill : null;
|
|
59
|
+
}
|
|
60
|
+
return skill?.name;
|
|
61
|
+
})
|
|
62
|
+
.filter((name: any): name is string => typeof name === 'string' && name.length > 0)
|
|
63
|
+
: [];
|
|
64
|
+
|
|
65
|
+
return Array.from(new Set([...tools, ...legacyTools]));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Shared context provider that fetches aiEmployees once and shares the data
|
|
70
|
+
* across RulesTab, TracingTab, AgentRunsTab, and AIEmployeeSelect.
|
|
71
|
+
*
|
|
72
|
+
* Also exposes each employee's configured tools so RulesTab can warn when a
|
|
73
|
+
* delegation rule exists but the leader hasn't added the corresponding
|
|
74
|
+
* delegate_<leader>_to_<sub> tool to its skillSettings.tools.
|
|
75
|
+
*/
|
|
76
|
+
export const AIEmployeesProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
77
|
+
const { data, loading, refresh } = useRequest({
|
|
78
|
+
url: 'aiEmployees:list',
|
|
79
|
+
params: { pageSize: 200 },
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const value = React.useMemo(() => {
|
|
83
|
+
const rawEmployees = (data as any)?.data || [];
|
|
84
|
+
const employees: AIEmployeeInfo[] = rawEmployees.map((emp: any) => {
|
|
85
|
+
const tools = extractToolNames(emp.skillSettings);
|
|
86
|
+
return {
|
|
87
|
+
username: emp.username,
|
|
88
|
+
nickname: emp.nickname || emp.username,
|
|
89
|
+
about: emp.about?.substring(0, 80),
|
|
90
|
+
tools,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const employeeMap = new Map<string, string>();
|
|
95
|
+
const toolNamesMap = new Map<string, Set<string>>();
|
|
96
|
+
for (const emp of employees) {
|
|
97
|
+
employeeMap.set(emp.username, emp.nickname);
|
|
98
|
+
toolNamesMap.set(emp.username, new Set(emp.tools));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { employees, employeeMap, toolNamesMap, loading, refresh };
|
|
102
|
+
}, [data, loading, refresh]);
|
|
103
|
+
|
|
104
|
+
return <AIEmployeesContext.Provider value={value}>{children}</AIEmployeesContext.Provider>;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Hook to access shared AI employees data.
|
|
109
|
+
*/
|
|
110
|
+
export const useAIEmployees = () => useContext(AIEmployeesContext);
|