plugin-agent-orchestrator 1.0.22 → 1.0.25

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 (103) 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 +8 -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 +60 -19
  34. package/src/client/AgentRunsTab.tsx +769 -764
  35. package/src/client/HarnessProfilesTab.tsx +257 -247
  36. package/src/client/RulesTab.tsx +787 -716
  37. package/src/client/TracingTab.tsx +9 -6
  38. package/src/client/plugin.tsx +34 -27
  39. package/src/client/skill-hub/components/ExecutionHistory.tsx +9 -8
  40. package/src/client/skill-hub/components/GitSkillImport.tsx +12 -5
  41. package/src/client/skill-hub/components/LoopSettings.tsx +2 -2
  42. package/src/client/skill-hub/components/SkillEditor.tsx +2 -2
  43. package/src/client/skill-hub/components/SkillManager.tsx +2 -2
  44. package/src/client/skill-hub/components/SkillMetrics.tsx +157 -124
  45. package/src/client/skill-hub/components/SkillTestPanel.tsx +14 -13
  46. package/src/client/skill-hub/index.tsx +58 -51
  47. package/src/client/skill-hub/locale.ts +1 -1
  48. package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +132 -99
  49. package/src/client/skill-hub/tools/registerSkillLoopCards.ts +71 -58
  50. package/src/client/tools/PlanApprovalCard.tsx +3 -2
  51. package/src/client/tools/registerOrchestratorCards.ts +17 -7
  52. package/src/client-v2/components/AIEmployeeSelect.tsx +47 -0
  53. package/src/client-v2/components/AIEmployeesContext.tsx +110 -0
  54. package/src/client-v2/components/AgentRunsTab.tsx +767 -0
  55. package/src/client-v2/components/HarnessProfilesTab.tsx +254 -0
  56. package/src/client-v2/components/RulesTab.tsx +782 -0
  57. package/src/client-v2/components/TracingTab.tsx +432 -0
  58. package/src/client-v2/hooks/useApiRequest.ts +114 -0
  59. package/src/client-v2/index.tsx +1 -0
  60. package/src/client-v2/pages/AgentRunsPage.tsx +13 -0
  61. package/src/client-v2/pages/ExecutionHistoryPage.tsx +10 -0
  62. package/src/client-v2/pages/HarnessProfilesPage.tsx +10 -0
  63. package/src/client-v2/pages/LoopSettingsPage.tsx +10 -0
  64. package/src/client-v2/pages/RulesPage.tsx +13 -0
  65. package/src/client-v2/pages/SkillDefinitionsPage.tsx +10 -0
  66. package/src/client-v2/pages/SkillMetricsPage.tsx +10 -0
  67. package/src/client-v2/pages/TracingPage.tsx +13 -0
  68. package/src/client-v2/plugin.tsx +70 -0
  69. package/src/client-v2/skill-hub/components/ExecutionHistory.tsx +196 -0
  70. package/src/client-v2/skill-hub/components/FileLinkList.tsx +37 -0
  71. package/src/client-v2/skill-hub/components/GitSkillImport.tsx +539 -0
  72. package/src/client-v2/skill-hub/components/LoopSettings.tsx +331 -0
  73. package/src/client-v2/skill-hub/components/SkillEditor.tsx +453 -0
  74. package/src/client-v2/skill-hub/components/SkillManager.tsx +174 -0
  75. package/src/client-v2/skill-hub/components/SkillMetrics.tsx +157 -0
  76. package/src/client-v2/skill-hub/components/SkillTestPanel.tsx +135 -0
  77. package/src/client-v2/skill-hub/locale.ts +13 -0
  78. package/src/client-v2/skill-hub/tools/loopTemplates.ts +52 -0
  79. package/src/client-v2/skill-hub/utils/jsonFields.ts +41 -0
  80. package/src/client-v2/utils/jsonFields.ts +41 -0
  81. package/src/locale/en-US.json +7 -0
  82. package/src/locale/vi-VN.json +7 -0
  83. package/src/locale/zh-CN.json +27 -0
  84. package/src/server/__tests__/agent-registry-service.test.ts +147 -0
  85. package/src/server/__tests__/code-validator.test.ts +63 -0
  86. package/src/server/__tests__/skill-execute.test.ts +33 -0
  87. package/src/server/__tests__/skill-settings.test.ts +63 -0
  88. package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +39 -0
  89. package/src/server/plugin.ts +62 -21
  90. package/src/server/services/AgentHarness.ts +49 -22
  91. package/src/server/services/AgentLoopController.ts +17 -6
  92. package/src/server/services/AgentLoopService.ts +1 -1
  93. package/src/server/services/AgentPlannerService.ts +10 -0
  94. package/src/server/services/AgentRegistryService.ts +89 -47
  95. package/src/server/services/CircuitBreaker.ts +10 -0
  96. package/src/server/services/CodeValidator.ts +237 -159
  97. package/src/server/services/SandboxRunner.ts +203 -189
  98. package/src/server/skill-hub/plugin.ts +933 -898
  99. package/src/server/tools/delegate-task.ts +12 -9
  100. package/src/server/tools/skill-execute.ts +194 -160
  101. package/src/server/utils/ai-manager.ts +24 -0
  102. package/src/server/utils/ctx-utils.ts +14 -0
  103. package/src/server/utils/skill-settings.ts +116 -0
@@ -1,51 +1,58 @@
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
+ (this as any).app.eventBus?.addEventListener?.('auth:tokenChanged', (event: Event) => {
43
+ const token = (event as CustomEvent<{ token?: string | null }>).detail?.token;
44
+ if (token) {
45
+ this.registerSkillUiCards();
46
+ }
47
+ });
48
+
49
+ await this.registerSkillUiCards();
50
+ }
51
+
52
+ private async registerSkillUiCards() {
53
+ await registerSkillLoopCards((this as any).app);
54
+ }
55
+ }
56
+
57
+ export { SkillManager, ExecutionHistory, SkillMetrics, LoopSettings };
58
+ export default PluginSkillHubClient;
@@ -1,4 +1,4 @@
1
- import { useApp } from '@nocobase/client';
1
+ import { useApp } from '@nocobase/client-v2';
2
2
  import { useCallback } from 'react';
3
3
  export const namespace = 'plugin-agent-orchestrator';
4
4
 
@@ -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 { useApp } from '@nocobase/client-v2';
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 useApp>['apiClient'] & {
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 = useApp().apiClient 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,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { Alert, Button, Card, Input, List, Space, Tag, Typography, message } from 'antd';
3
- import { ToolsUIProperties, useAPIClient } from '@nocobase/client';
3
+ import { ToolsUIProperties } from '@nocobase/client';
4
+ import { useApp } from '@nocobase/client-v2';
4
5
 
5
6
  const { Paragraph, Text } = Typography;
6
7
 
@@ -18,7 +19,7 @@ const summarizeArgsPlan = (plan: any[]) =>
18
19
  }));
19
20
 
20
21
  export const PlanApprovalCard: React.FC<ToolsUIProperties> = ({ toolCall, decisions }) => {
21
- const api = useAPIClient();
22
+ const api = useApp().apiClient;
22
23
  const rawArgs = (toolCall.args as Record<string, any>) || {};
23
24
  const runId = rawArgs.runId;
24
25
  const [detail, setDetail] = React.useState<any>(null);
@@ -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
+ };