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.
Files changed (96) hide show
  1. package/client-v2.d.ts +2 -0
  2. package/client-v2.js +1 -0
  3. package/dist/client/index.js +1 -1
  4. package/dist/client-v2/214.723affb37c13bf7a.js +10 -0
  5. package/dist/client-v2/264.0533912e6c5ea2d7.js +10 -0
  6. package/dist/client-v2/41.1805b2edfaa4afe2.js +10 -0
  7. package/dist/client-v2/418.5ae055abf141820e.js +10 -0
  8. package/dist/client-v2/619.d99d3c9e61c99064.js +10 -0
  9. package/dist/client-v2/70.a15d7fcec7c41768.js +10 -0
  10. package/dist/client-v2/892.72db4161511c8a16.js +10 -0
  11. package/dist/client-v2/926.87f660b670d85bcc.js +10 -0
  12. package/dist/client-v2/index.js +10 -0
  13. package/dist/externalVersion.js +7 -6
  14. package/dist/locale/en-US.json +7 -0
  15. package/dist/locale/vi-VN.json +7 -0
  16. package/dist/locale/zh-CN.json +27 -0
  17. package/dist/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.js +63 -0
  18. package/dist/server/plugin.js +32 -1
  19. package/dist/server/services/AgentHarness.js +52 -27
  20. package/dist/server/services/AgentLoopController.js +8 -2
  21. package/dist/server/services/AgentLoopService.js +1 -1
  22. package/dist/server/services/AgentRegistryService.js +53 -42
  23. package/dist/server/services/CircuitBreaker.js +7 -2
  24. package/dist/server/services/CodeValidator.js +48 -14
  25. package/dist/server/services/SandboxRunner.js +18 -14
  26. package/dist/server/skill-hub/plugin.js +44 -17
  27. package/dist/server/tools/delegate-task.js +7 -2
  28. package/dist/server/tools/skill-execute.js +33 -2
  29. package/dist/server/utils/ai-manager.js +51 -0
  30. package/dist/server/utils/ctx-utils.js +11 -0
  31. package/dist/server/utils/skill-settings.js +122 -0
  32. package/package.json +49 -45
  33. package/src/client/AIEmployeesContext.tsx +51 -14
  34. package/src/client/AgentRunsTab.tsx +767 -764
  35. package/src/client/HarnessProfilesTab.tsx +254 -247
  36. package/src/client/RulesTab.tsx +780 -716
  37. package/src/client/TracingTab.tsx +1 -0
  38. package/src/client/plugin.tsx +34 -27
  39. package/src/client/skill-hub/components/GitSkillImport.tsx +10 -3
  40. package/src/client/skill-hub/components/SkillMetrics.tsx +157 -124
  41. package/src/client/skill-hub/index.tsx +58 -51
  42. package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +132 -99
  43. package/src/client/skill-hub/tools/registerSkillLoopCards.ts +71 -58
  44. package/src/client/tools/registerOrchestratorCards.ts +17 -7
  45. package/src/client-v2/components/AIEmployeeSelect.tsx +47 -0
  46. package/src/client-v2/components/AIEmployeesContext.tsx +110 -0
  47. package/src/client-v2/components/AgentRunsTab.tsx +767 -0
  48. package/src/client-v2/components/HarnessProfilesTab.tsx +254 -0
  49. package/src/client-v2/components/RulesTab.tsx +782 -0
  50. package/src/client-v2/components/TracingTab.tsx +432 -0
  51. package/src/client-v2/hooks/useApiRequest.ts +114 -0
  52. package/src/client-v2/index.tsx +1 -0
  53. package/src/client-v2/pages/AgentRunsPage.tsx +13 -0
  54. package/src/client-v2/pages/ExecutionHistoryPage.tsx +10 -0
  55. package/src/client-v2/pages/HarnessProfilesPage.tsx +10 -0
  56. package/src/client-v2/pages/LoopSettingsPage.tsx +10 -0
  57. package/src/client-v2/pages/RulesPage.tsx +13 -0
  58. package/src/client-v2/pages/SkillDefinitionsPage.tsx +10 -0
  59. package/src/client-v2/pages/SkillMetricsPage.tsx +10 -0
  60. package/src/client-v2/pages/TracingPage.tsx +13 -0
  61. package/src/client-v2/plugin.tsx +70 -0
  62. package/src/client-v2/skill-hub/components/ExecutionHistory.tsx +196 -0
  63. package/src/client-v2/skill-hub/components/FileLinkList.tsx +37 -0
  64. package/src/client-v2/skill-hub/components/GitSkillImport.tsx +539 -0
  65. package/src/client-v2/skill-hub/components/LoopSettings.tsx +331 -0
  66. package/src/client-v2/skill-hub/components/SkillEditor.tsx +453 -0
  67. package/src/client-v2/skill-hub/components/SkillManager.tsx +174 -0
  68. package/src/client-v2/skill-hub/components/SkillMetrics.tsx +157 -0
  69. package/src/client-v2/skill-hub/components/SkillTestPanel.tsx +135 -0
  70. package/src/client-v2/skill-hub/locale.ts +13 -0
  71. package/src/client-v2/skill-hub/tools/loopTemplates.ts +52 -0
  72. package/src/client-v2/skill-hub/utils/jsonFields.ts +41 -0
  73. package/src/client-v2/utils/jsonFields.ts +41 -0
  74. package/src/locale/en-US.json +7 -0
  75. package/src/locale/vi-VN.json +7 -0
  76. package/src/locale/zh-CN.json +27 -0
  77. package/src/server/__tests__/agent-registry-service.test.ts +147 -0
  78. package/src/server/__tests__/code-validator.test.ts +63 -0
  79. package/src/server/__tests__/skill-execute.test.ts +33 -0
  80. package/src/server/__tests__/skill-settings.test.ts +63 -0
  81. package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +39 -0
  82. package/src/server/plugin.ts +62 -21
  83. package/src/server/services/AgentHarness.ts +49 -22
  84. package/src/server/services/AgentLoopController.ts +17 -6
  85. package/src/server/services/AgentLoopService.ts +1 -1
  86. package/src/server/services/AgentPlannerService.ts +10 -0
  87. package/src/server/services/AgentRegistryService.ts +89 -47
  88. package/src/server/services/CircuitBreaker.ts +10 -0
  89. package/src/server/services/CodeValidator.ts +237 -159
  90. package/src/server/services/SandboxRunner.ts +203 -189
  91. package/src/server/skill-hub/plugin.ts +933 -898
  92. package/src/server/tools/delegate-task.ts +12 -9
  93. package/src/server/tools/skill-execute.ts +194 -160
  94. package/src/server/utils/ai-manager.ts +24 -0
  95. package/src/server/utils/ctx-utils.ts +14 -0
  96. 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
- 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
+ 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
- export async function registerSkillLoopCards(app: any) {
17
- const toolsManager = app.aiManager?.toolsManager;
18
- if (!toolsManager) return;
19
-
20
- try {
21
- const skillsResponse = await app.apiClient.request({
22
- url: 'skillDefinitions:list',
23
- params: {
24
- filter: { enabled: true },
25
- fields: ['id', 'name', 'autoCall', 'interactionSchema'],
26
- pageSize: 500,
27
- },
28
- });
29
-
30
- let loopSkillIds = new Set<string>();
31
- try {
32
- const loopConfigsResponse = await app.apiClient.request({
33
- url: 'skillLoopConfigs:list',
34
- params: {
35
- filter: { enabled: true },
36
- fields: ['skillId'],
37
- pageSize: 500,
38
- },
39
- });
40
- loopSkillIds = new Set(extractList(loopConfigsResponse.data).map((config: any) => String(config.skillId)));
41
- if (loopSkillIds.size > 0) {
42
- toolsManager.registerTools('skill_hub_execute', { ui: { card: SkillHubCard } });
43
- }
44
- } catch {
45
- // Older deployments may not have the collection before migration/sync.
46
- }
47
-
48
- const skills = extractList(skillsResponse.data);
49
- for (const skill of skills) {
50
- const hasLoopConfig = loopSkillIds.has(String(skill.id));
51
- const hasLegacySchema = !skill.autoCall && !!parseJsonText(skill.interactionSchema, null);
52
- if (!hasLoopConfig && !hasLegacySchema) continue;
53
- toolsManager.registerTools(`skill_hub_${sanitize(skill.name)}`, { ui: { card: SkillHubCard } });
54
- }
55
- } catch {
56
- // user without ACL or backend unavailable - skip silently
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
- export async function registerOrchestratorCards(app: any) {
4
- const toolsManager = app.aiManager?.toolsManager;
5
- if (!toolsManager) return;
6
- toolsManager.registerTools('orchestrator_execute_plan', { ui: { card: PlanApprovalCard } });
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);