plugin-agent-orchestrator 1.0.15 → 1.0.17

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 (66) hide show
  1. package/README.md +96 -0
  2. package/dist/externalVersion.js +6 -6
  3. package/dist/server/plugin.js +11 -1
  4. package/dist/server/services/SkillManager.js +0 -39
  5. package/dist/server/skill-hub/plugin.js +3 -3
  6. package/dist/server/tools/delegate-task.js +60 -4
  7. package/dist/server/tools/external-rag-search.js +140 -0
  8. package/dist/server/tools/skill-execute.js +1 -2
  9. package/package.json +2 -2
  10. package/src/server/plugin.ts +104 -94
  11. package/src/server/services/SkillManager.ts +13 -53
  12. package/src/server/skill-hub/plugin.ts +3 -3
  13. package/src/server/tools/delegate-task.ts +77 -6
  14. package/src/server/tools/external-rag-search.ts +128 -0
  15. package/src/server/tools/skill-execute.ts +1 -2
  16. package/dist/client/AIEmployeeSelect.d.ts +0 -11
  17. package/dist/client/AIEmployeesContext.d.ts +0 -30
  18. package/dist/client/OrchestratorSettings.d.ts +0 -3
  19. package/dist/client/RulesTab.d.ts +0 -2
  20. package/dist/client/TracingTab.d.ts +0 -2
  21. package/dist/client/index.d.ts +0 -1
  22. package/dist/client/plugin.d.ts +0 -6
  23. package/dist/client/skill-hub/components/ExecutionHistory.d.ts +0 -2
  24. package/dist/client/skill-hub/components/ExecutionProgress.d.ts +0 -20
  25. package/dist/client/skill-hub/components/GitSkillImport.d.ts +0 -7
  26. package/dist/client/skill-hub/components/SkillEditor.d.ts +0 -7
  27. package/dist/client/skill-hub/components/SkillManager.d.ts +0 -2
  28. package/dist/client/skill-hub/components/SkillMetrics.d.ts +0 -2
  29. package/dist/client/skill-hub/components/SkillTestPanel.d.ts +0 -7
  30. package/dist/client/skill-hub/index.d.ts +0 -10
  31. package/dist/client/skill-hub/locale.d.ts +0 -3
  32. package/dist/client/skill-hub/tools/InteractionSchemasProvider.d.ts +0 -19
  33. package/dist/client/skill-hub/tools/SkillHubCard.d.ts +0 -3
  34. package/dist/client/skill-hub/utils/jsonFields.d.ts +0 -3
  35. package/dist/index.d.ts +0 -2
  36. package/dist/server/collections/agent-execution-spans.d.ts +0 -9
  37. package/dist/server/collections/orchestrator-config.d.ts +0 -2
  38. package/dist/server/collections/orchestrator-logs.d.ts +0 -8
  39. package/dist/server/collections/skill-definitions.d.ts +0 -2
  40. package/dist/server/collections/skill-executions.d.ts +0 -2
  41. package/dist/server/collections/skill-worker-configs.d.ts +0 -2
  42. package/dist/server/index.d.ts +0 -1
  43. package/dist/server/migrations/20260423000000-add-progress-fields.d.ts +0 -4
  44. package/dist/server/migrations/20260425000000-add-interaction-schema.d.ts +0 -4
  45. package/dist/server/migrations/20260427000000-add-tracing-detail-fields.d.ts +0 -7
  46. package/dist/server/migrations/20260427000000-change-packages-to-text.d.ts +0 -4
  47. package/dist/server/migrations/20260427000001-change-other-json-to-text.d.ts +0 -4
  48. package/dist/server/migrations/20260429000000-add-llm-fields.d.ts +0 -7
  49. package/dist/server/migrations/20260429000000-fix-inputargs-json-to-text.d.ts +0 -16
  50. package/dist/server/migrations/20260503000000-add-orchestrator-trace-fields.d.ts +0 -7
  51. package/dist/server/plugin.d.ts +0 -14
  52. package/dist/server/resources/tracing.d.ts +0 -7
  53. package/dist/server/services/CodeValidator.d.ts +0 -32
  54. package/dist/server/services/ExecutionSpanService.d.ts +0 -44
  55. package/dist/server/services/FileManager.d.ts +0 -28
  56. package/dist/server/services/SandboxRunner.d.ts +0 -41
  57. package/dist/server/services/SkillManager.d.ts +0 -6
  58. package/dist/server/services/SkillRepositoryService.d.ts +0 -22
  59. package/dist/server/services/WorkerEnvManager.d.ts +0 -26
  60. package/dist/server/skill-hub/actions/git-import.d.ts +0 -21
  61. package/dist/server/skill-hub/mcp/McpController.d.ts +0 -15
  62. package/dist/server/skill-hub/plugin.d.ts +0 -58
  63. package/dist/server/skill-hub/tasks/SkillExecutionTask.d.ts +0 -16
  64. package/dist/server/skill-hub/utils/json-fields.d.ts +0 -7
  65. package/dist/server/tools/delegate-task.d.ts +0 -19
  66. package/dist/server/tools/skill-execute.d.ts +0 -36
@@ -1,94 +1,104 @@
1
- import { Plugin } from '@nocobase/server';
2
- import path from 'path';
3
- import { createDelegateToolsProvider } from './tools/delegate-task';
4
- import { registerTracingResource } from './resources/tracing';
5
- import SkillHubSubFeature from './skill-hub/plugin';
6
-
7
- export class PluginAgentOrchestratorServer extends Plugin {
8
- skillHub: SkillHubSubFeature;
9
-
10
- async afterAdd() {
11
- this.skillHub = new SkillHubSubFeature(this);
12
- }
13
-
14
- async beforeLoad() {
15
- // Import collection definitions
16
- (this as any).db.import({ directory: path.resolve(__dirname, 'collections') });
17
-
18
- (this as any).db.addMigrations({
19
- namespace: (this as any).name,
20
- directory: path.resolve(__dirname, 'migrations'),
21
- context: { plugin: this },
22
- });
23
- }
24
-
25
- async load() {
26
- await this.skillHub.load();
27
-
28
- // --- ACL ---
29
- (this as any).app.acl.registerSnippet({
30
- name: `pm.${(this as any).name}`,
31
- actions: ['orchestratorConfig:*', 'orchestratorTracing:*', 'agentExecutionSpans:*', 'skillDefinitions:*', 'skillExecutions:*', 'skillHub:*', 'skillWorkerConfigs:*'],
32
- });
33
-
34
- // --- Register Dynamic Tools ---
35
- // Each configured sub-agent becomes a callable tool for its leader.
36
- // Uses createReactAgent (LangGraph public API) instead of private AIEmployee class.
37
- // Tools are registered via app.aiManager.toolsManager (public API from @nocobase/ai core).
38
- const toolsManager = (this as any).app.aiManager.toolsManager;
39
- toolsManager.registerDynamicTools(createDelegateToolsProvider(this));
40
-
41
- // --- Register Tracing Resource (Phase 5) ---
42
- // Custom read-only resource for the Swarm Tracing admin page.
43
- registerTracingResource(this);
44
-
45
- // --- Log Retention ---
46
- // Daily prune of orchestratorLogs / agentExecutionSpans to keep tables bounded.
47
- // Override window via env: ORCHESTRATOR_LOG_RETENTION_DAYS (default 30).
48
- (this as any).app.cronJobManager.addJob({
49
- cronTime: '0 30 2 * * *',
50
- onTick: async () => {
51
- try {
52
- const days = Number(process.env.ORCHESTRATOR_LOG_RETENTION_DAYS || 30);
53
- if (!Number.isFinite(days) || days <= 0) return;
54
- const cutoff = new Date(Date.now() - days * 86400000);
55
- const repo = (this as any).db.getRepository('orchestratorLogs');
56
- const spansRepo = (this as any).db.getRepository('agentExecutionSpans');
57
- const deletedLogs = repo
58
- ? await repo.destroy({
59
- filter: { createdAt: { $lt: cutoff.toISOString() } },
60
- })
61
- : 0;
62
- const deletedSpans = spansRepo
63
- ? await spansRepo.destroy({
64
- filter: { createdAt: { $lt: cutoff.toISOString() } },
65
- })
66
- : 0;
67
- (this as any).app.log.info(
68
- `[AgentOrchestrator] Pruned ${deletedLogs} orchestratorLogs and ${deletedSpans} agentExecutionSpans rows older than ${days} day(s).`,
69
- );
70
- } catch (e) {
71
- (this as any).app.log.error('[AgentOrchestrator] Log retention job failed', e);
72
- }
73
- },
74
- });
75
-
76
- // NOTE: The createReactAgent approach does NOT create aiConversation records,
77
- // so there is no need for a middleware to hide "headless" conversations.
78
- // If future versions need conversation logging, add it here.
79
- }
80
-
81
- async install() {
82
- await this.skillHub.install();
83
- }
84
-
85
- async afterEnable() {}
86
- async afterDisable() {}
87
- async remove() {}
88
-
89
- async beforeStop() {
90
- await this.skillHub.beforeStop();
91
- }
92
- }
93
-
94
- export default PluginAgentOrchestratorServer;
1
+ import { Plugin } from '@nocobase/server';
2
+ import path from 'path';
3
+ import { createDelegateToolsProvider } from './tools/delegate-task';
4
+ import { createExternalRagSearchTool } from './tools/external-rag-search';
5
+ import { registerTracingResource } from './resources/tracing';
6
+ import SkillHubSubFeature from './skill-hub/plugin';
7
+
8
+ export class PluginAgentOrchestratorServer extends Plugin {
9
+ skillHub: SkillHubSubFeature;
10
+
11
+ async afterAdd() {
12
+ this.skillHub = new SkillHubSubFeature(this);
13
+ }
14
+
15
+ async beforeLoad() {
16
+ // Import collection definitions
17
+ (this as any).db.import({ directory: path.resolve(__dirname, 'collections') });
18
+
19
+ (this as any).db.addMigrations({
20
+ namespace: (this as any).name,
21
+ directory: path.resolve(__dirname, 'migrations'),
22
+ context: { plugin: this },
23
+ });
24
+ }
25
+
26
+ async load() {
27
+ await this.skillHub.load();
28
+
29
+ // --- ACL ---
30
+ (this as any).app.acl.registerSnippet({
31
+ name: `pm.${(this as any).name}`,
32
+ actions: [
33
+ 'orchestratorConfig:*',
34
+ 'orchestratorTracing:*',
35
+ 'agentExecutionSpans:*',
36
+ 'skillDefinitions:*',
37
+ 'skillExecutions:*',
38
+ 'skillHub:*',
39
+ 'skillWorkerConfigs:*',
40
+ ],
41
+ });
42
+
43
+ // --- Register Dynamic Tools ---
44
+ // Each configured sub-agent becomes a callable tool for its leader.
45
+ // Uses createReactAgent (LangGraph public API) instead of private AIEmployee class.
46
+ // Tools are registered via app.aiManager.toolsManager (public API from @nocobase/ai core).
47
+ const toolsManager = (this as any).app.aiManager.toolsManager;
48
+ toolsManager.registerTools(createExternalRagSearchTool(this));
49
+ toolsManager.registerDynamicTools(createDelegateToolsProvider(this));
50
+
51
+ // --- Register Tracing Resource (Phase 5) ---
52
+ // Custom read-only resource for the Swarm Tracing admin page.
53
+ registerTracingResource(this);
54
+
55
+ // --- Log Retention ---
56
+ // Daily prune of orchestratorLogs / agentExecutionSpans to keep tables bounded.
57
+ // Override window via env: ORCHESTRATOR_LOG_RETENTION_DAYS (default 30).
58
+ (this as any).app.cronJobManager.addJob({
59
+ cronTime: '0 30 2 * * *',
60
+ onTick: async () => {
61
+ try {
62
+ const days = Number(process.env.ORCHESTRATOR_LOG_RETENTION_DAYS || 30);
63
+ if (!Number.isFinite(days) || days <= 0) return;
64
+ const cutoff = new Date(Date.now() - days * 86400000);
65
+ const repo = (this as any).db.getRepository('orchestratorLogs');
66
+ const spansRepo = (this as any).db.getRepository('agentExecutionSpans');
67
+ const deletedLogs = repo
68
+ ? await repo.destroy({
69
+ filter: { createdAt: { $lt: cutoff.toISOString() } },
70
+ })
71
+ : 0;
72
+ const deletedSpans = spansRepo
73
+ ? await spansRepo.destroy({
74
+ filter: { createdAt: { $lt: cutoff.toISOString() } },
75
+ })
76
+ : 0;
77
+ (this as any).app.log.info(
78
+ `[AgentOrchestrator] Pruned ${deletedLogs} orchestratorLogs and ${deletedSpans} agentExecutionSpans rows older than ${days} day(s).`,
79
+ );
80
+ } catch (e) {
81
+ (this as any).app.log.error('[AgentOrchestrator] Log retention job failed', e);
82
+ }
83
+ },
84
+ });
85
+
86
+ // NOTE: The createReactAgent approach does NOT create aiConversation records,
87
+ // so there is no need for a middleware to hide "headless" conversations.
88
+ // If future versions need conversation logging, add it here.
89
+ }
90
+
91
+ async install() {
92
+ await this.skillHub.install();
93
+ }
94
+
95
+ async afterEnable() {}
96
+ async afterDisable() {}
97
+ async remove() {}
98
+
99
+ async beforeStop() {
100
+ await this.skillHub.beforeStop();
101
+ }
102
+ }
103
+
104
+ export default PluginAgentOrchestratorServer;
@@ -1,11 +1,11 @@
1
- import { Database } from '@nocobase/database';
2
- import { stringifyJsonText } from '../skill-hub/utils/json-fields';
1
+ import { Database } from '@nocobase/database';
2
+ import { stringifyJsonText } from '../skill-hub/utils/json-fields';
3
3
 
4
4
  export class SkillManager {
5
5
  constructor(private db: Database) {}
6
6
 
7
7
  async seedDefaults() {
8
- const repo = (this as any).db.getRepository('skillDefinitions');
8
+ const repo = this.db.getRepository('skillDefinitions');
9
9
 
10
10
  const seeds = [
11
11
  {
@@ -203,16 +203,16 @@ export class SkillManager {
203
203
 
204
204
  for (const seed of seeds) {
205
205
  try {
206
- const count = await repo.count({ filter: { name: seed.name } });
207
- if (count === 0) {
208
- await repo.create({
209
- values: {
210
- ...seed,
211
- inputSchema: stringifyJsonText(seed.inputSchema),
212
- packages: stringifyJsonText(seed.packages, []),
213
- },
214
- });
215
- }
206
+ const count = await repo.count({ filter: { name: seed.name } });
207
+ if (count === 0) {
208
+ await repo.create({
209
+ values: {
210
+ ...seed,
211
+ inputSchema: stringifyJsonText(seed.inputSchema),
212
+ packages: stringifyJsonText(seed.packages, []),
213
+ },
214
+ });
215
+ }
216
216
  } catch (err) {
217
217
  console.error(`[import-skill] Failed to insert ${seed.name}:`, err);
218
218
  // Skip if already exists (unique constraint on name)
@@ -330,46 +330,6 @@ doc.build(story)
330
330
  print('Generated: report.pdf')
331
331
  `;
332
332
 
333
- const SEED_PPTX = `import os, json
334
- from pptx import Presentation
335
- from pptx.util import Inches, Pt
336
- from pptx.enum.text import PP_ALIGN
337
-
338
- title = json.loads('''{{title}}''') if '{{title}}'.startswith('"') else '{{title}}'
339
- subtitle_raw = '''{{subtitle}}'''
340
- subtitle = json.loads(subtitle_raw) if subtitle_raw.startswith('"') else subtitle_raw if subtitle_raw != '{{' + 'subtitle}}' else ''
341
- slides_data = json.loads('''{{slides}}''')
342
-
343
- prs = Presentation()
344
- prs.slide_width = Inches(13.333)
345
- prs.slide_height = Inches(7.5)
346
-
347
- # Title slide
348
- slide = prs.slides.add_slide(prs.slide_layouts[0])
349
- slide.shapes.title.text = title
350
- if subtitle and slide.placeholders[1]:
351
- slide.placeholders[1].text = subtitle
352
-
353
- # Content slides
354
- for s in slides_data:
355
- slide = prs.slides.add_slide(prs.slide_layouts[1])
356
- slide.shapes.title.text = s.get('title', '')
357
- body = slide.placeholders[1].text_frame
358
- body.clear()
359
- for i, bullet in enumerate(s.get('bullets', [])):
360
- if i == 0:
361
- body.paragraphs[0].text = bullet
362
- else:
363
- p = body.add_paragraph()
364
- p.text = bullet
365
- body.paragraphs[-1].font.size = Pt(18)
366
-
367
- output_dir = os.environ.get('OUTPUT_DIR', '/output')
368
- filepath = os.path.join(output_dir, 'presentation.pptx')
369
- prs.save(filepath)
370
- print('Generated: presentation.pptx')
371
- `;
372
-
373
333
  const SEED_CHART = `import os, json
374
334
  import matplotlib
375
335
  matplotlib.use('Agg')
@@ -543,7 +543,7 @@ export class SkillHubSubFeature {
543
543
  filter: { id: skill.get('id'), enabled: true },
544
544
  });
545
545
  if (!latestSkill) {
546
- return { error: `Skill "${skill.get('name')}" is no longer available` };
546
+ return { status: 'error', content: `Skill "${skill.get('name')}" is no longer available` };
547
547
  }
548
548
  const result = await this.executeSkill(latestSkill, args, toolCtx);
549
549
  return {
@@ -582,7 +582,7 @@ export class SkillHubSubFeature {
582
582
  const repo = (this as any).db.getRepository('skillExecutions');
583
583
 
584
584
  const outdated = await repo.find({
585
- where: { createdAt: { $lt: cutoff } }
585
+ filter: { createdAt: { $lt: cutoff } }
586
586
  });
587
587
 
588
588
  if (outdated.length > 0) {
@@ -638,7 +638,7 @@ export class SkillHubSubFeature {
638
638
  const hours = config ? config.get('retentionHours') : 24;
639
639
  if (hours > 0) {
640
640
  const cutoff = new Date(Date.now() - hours * 60 * 60 * 1000);
641
- const results = await repo.find({ where: { createdAt: { $lt: cutoff } }, fields: ['id'] });
641
+ const results = await repo.find({ filter: { createdAt: { $lt: cutoff } }, fields: ['id'] });
642
642
  for (const rec of results) {
643
643
  await rec.destroy();
644
644
  }
@@ -18,6 +18,12 @@ import {
18
18
  * Used to prevent circular/recursive delegation chains.
19
19
  */
20
20
  const ORCHESTRATOR_DEPTH_KEY = '__orchestratorDepth';
21
+ /**
22
+ * Context key for tracking the full delegation path.
23
+ * Used to detect and prevent circular delegation chains.
24
+ */
25
+ const ORCHESTRATOR_PATH_KEY = '__orchestratorPath';
26
+
21
27
 
22
28
  /** Max sub-agents that the dispatch tool runs concurrently in one call. */
23
29
  const MAX_DISPATCH_CONCURRENCY = 5;
@@ -794,8 +800,31 @@ async function invokeDelegateTask(
794
800
  // Capturing the values once here keeps log/audit fields stable.
795
801
  const ctxSnapshot = captureCtxSnapshot(ctx);
796
802
 
797
- // --- P1: Depth enforcement ---
803
+ // --- P1: Depth enforcement & Circular Delegation Detection ---
798
804
  const currentDepth: number = (ctx as any)[ORCHESTRATOR_DEPTH_KEY] ?? 0;
805
+ const currentPath: string[] = (ctx as any)[ORCHESTRATOR_PATH_KEY] ?? [leaderUsername];
806
+
807
+ if (currentPath.includes(subAgentUsername)) {
808
+ const loopChain = [...currentPath, subAgentUsername].join(' -> ');
809
+ await logDelegation(ctx, plugin, {
810
+ leaderUsername,
811
+ subAgentUsername,
812
+ toolName,
813
+ task,
814
+ context,
815
+ result: '',
816
+ status: 'error',
817
+ depth: currentDepth,
818
+ durationMs: 0,
819
+ error: `Circular delegation detected: ${loopChain}.`,
820
+ snapshot: ctxSnapshot,
821
+ });
822
+ return {
823
+ status: 'error' as const,
824
+ content: `Circular delegation detected: ${loopChain}. Execution aborted to prevent infinite reasoning loops.`,
825
+ };
826
+ }
827
+
799
828
  if (currentDepth >= maxDepth) {
800
829
  await logDelegation(ctx, plugin, {
801
830
  leaderUsername,
@@ -943,8 +972,7 @@ async function invokeDelegateTask(
943
972
  if (
944
973
  !employeeSkill ||
945
974
  isDelegateToolName(plugin, entryName) ||
946
- employeeSkill.autoCall !== true ||
947
- toolEntry.defaultPermission !== 'ALLOW'
975
+ employeeSkill.autoCall !== true
948
976
  ) {
949
977
  continue;
950
978
  }
@@ -955,9 +983,15 @@ async function invokeDelegateTask(
955
983
  description: toolEntry.definition.description || entryName,
956
984
  schema: (toolEntry.definition.schema || z.object({})) as any,
957
985
  func: async (toolArgs) => {
958
- // Forward the invoke with depth tracking
986
+ // Forward the invoke with depth tracking, circular path tracking and identity overrides
959
987
  const invokeCtx = Object.create(ctx);
960
988
  (invokeCtx as any)[ORCHESTRATOR_DEPTH_KEY] = currentDepth + 1;
989
+ (invokeCtx as any)[ORCHESTRATOR_PATH_KEY] = [...currentPath, subAgentUsername];
990
+ (invokeCtx as any)._currentAIEmployee = subAgentUsername;
991
+ if (ctx.state) {
992
+ invokeCtx.state = Object.create(ctx.state);
993
+ invokeCtx.state.currentAIEmployee = subAgentUsername;
994
+ }
961
995
  const toolStartedAt = Date.now();
962
996
  const isSkillHubTool = entryName === 'skill_hub_execute' || entryName.startsWith('skill_hub_');
963
997
  const toolSpan = await spanService.create({
@@ -1046,13 +1080,37 @@ async function invokeDelegateTask(
1046
1080
  });
1047
1081
 
1048
1082
  // --- Step 4: Construct messages ---
1049
- const systemPrompt =
1083
+ let systemPrompt =
1050
1084
  subAgentEmployee.chatSettings?.systemPrompt ||
1051
1085
  subAgentEmployee.bio ||
1052
1086
  `You are an AI assistant named "${subAgentEmployee.nickname || subAgentUsername}". ${
1053
1087
  subAgentEmployee.about || ''
1054
1088
  }`;
1055
1089
 
1090
+ // --- Step 4b: Inject shared context from Knowledge Base (soft dependency) ---
1091
+ // If plugin-knowledge-base is installed, inject the session context summary
1092
+ // so the sub-agent is aware of findings from previous agents in this run.
1093
+ try {
1094
+ const kbPlugin = ctx.app.pm.get('plugin-knowledge-base') as any;
1095
+ if (kbPlugin?.sessionContext) {
1096
+ const sessionId =
1097
+ ctx.action?.params?.values?.sessionId ||
1098
+ ctx.action?.params?.sessionId ||
1099
+ ctx.state?.sessionId;
1100
+
1101
+ const contextSummary = await kbPlugin.sessionContext.buildSummary(
1102
+ { rootRunId, ...(sessionId ? { sessionId } : {}) },
1103
+ 6000,
1104
+ );
1105
+ if (contextSummary) {
1106
+ systemPrompt += `\n\n<shared_context>\nThe following context was shared by other agents in this workflow. Use it to avoid redundant work:\n${contextSummary}\n</shared_context>`;
1107
+ }
1108
+ }
1109
+ } catch (e: any) {
1110
+ // Graceful fallback — never block delegation due to context injection failure.
1111
+ ctx.app.log?.debug?.(`[AgentOrchestrator] Shared context injection skipped: ${e.message}`);
1112
+ }
1113
+
1056
1114
  const combinedTask = context ? `Task: ${task}\n\nContext Provided:\n${context}` : `Task: ${task}`;
1057
1115
 
1058
1116
  // --- Step 5: Execute with timeout + abort ---
@@ -1159,9 +1217,22 @@ async function invokeDelegateTask(
1159
1217
  },
1160
1218
  });
1161
1219
 
1220
+ const diagnosticTrace = trace
1221
+ .filter((t) => t.type === 'tool_error' || t.type === 'error')
1222
+ .map((t) => `[${t.toolName ? `Tool: ${t.toolName}` : 'Sub-agent'}] Error: ${t.content || t.title}`)
1223
+ .join('\n');
1224
+
1225
+ const formattedError = [
1226
+ `Sub-agent "${subAgentUsername}" failed execution: ${e.message}`,
1227
+ diagnosticTrace ? `\nDiagnostic Details of internal failures:\n${diagnosticTrace}` : '',
1228
+ `Suggestion: Review the tool parameters above or try dividing the task into simpler independent tasks.`,
1229
+ ]
1230
+ .filter(Boolean)
1231
+ .join('\n');
1232
+
1162
1233
  return {
1163
1234
  status: 'error' as const,
1164
- content: `Sub-agent "${subAgentUsername}" failed: ${e.message}`,
1235
+ content: formattedError,
1165
1236
  };
1166
1237
  }
1167
1238
  }
@@ -0,0 +1,128 @@
1
+ import { z } from 'zod';
2
+
3
+ const TOOL_NAME = 'external_rag_search';
4
+ const MAX_CONTENT_LENGTH = 4000;
5
+
6
+ function truncate(value: unknown, max = MAX_CONTENT_LENGTH) {
7
+ const text = typeof value === 'string' ? value : value == null ? '' : String(value);
8
+ return text.length > max ? `${text.slice(0, max)}...` : text;
9
+ }
10
+
11
+ function firstString(...values: unknown[]) {
12
+ for (const value of values) {
13
+ if (typeof value === 'string' && value.trim()) {
14
+ return value.trim();
15
+ }
16
+ if (typeof value === 'number' && Number.isFinite(value)) {
17
+ return String(value);
18
+ }
19
+ }
20
+ return undefined;
21
+ }
22
+
23
+ function normalizeResult(result: any) {
24
+ const metadata = result?.metadata ?? {};
25
+ const sourceId = firstString(
26
+ result?.id,
27
+ metadata.id,
28
+ metadata.sourceId,
29
+ metadata.documentId,
30
+ metadata.docId,
31
+ metadata.fileId,
32
+ metadata.recordId,
33
+ );
34
+ const filename = firstString(metadata.filename, metadata.fileName, metadata.name, metadata.title, metadata.source);
35
+
36
+ return {
37
+ content: truncate(result?.content),
38
+ score: Number(result?.rerankScore ?? result?.score ?? result?.vectorScore ?? 0),
39
+ knowledgeBaseId: result?.knowledgeBaseId,
40
+ knowledgeBaseName: result?.knowledgeBaseName,
41
+ source: {
42
+ id: sourceId,
43
+ filename,
44
+ url: firstString(metadata.url, metadata.fileUrl, metadata.sourceUrl),
45
+ collection: firstString(metadata.collection, metadata.collectionName),
46
+ recordId: firstString(metadata.recordId, metadata.rowId),
47
+ },
48
+ metadata,
49
+ };
50
+ }
51
+
52
+ export function createExternalRagSearchTool(plugin: any) {
53
+ return {
54
+ scope: 'CUSTOM' as const,
55
+ execution: 'backend' as const,
56
+ defaultPermission: 'ALLOW' as const,
57
+
58
+ introduction: {
59
+ title: 'External RAG Search',
60
+ about:
61
+ 'Search NocoBase knowledge bases through plugin-knowledge-base, including EXTERNAL_RAG services that own chunking, embedding, and retrieval.',
62
+ },
63
+
64
+ definition: {
65
+ name: TOOL_NAME,
66
+ description: `Search configured knowledge bases for relevant context. Use this before answering questions that require documents, files, or datasource-backed knowledge.
67
+
68
+ The search may be served by an external RAG service. Results include content plus source identifiers such as id, filename, collection, and recordId when the external service provides them.`,
69
+ schema: z.object({
70
+ query: z.string().min(1).describe('Natural-language search query.'),
71
+ knowledgeBaseIds: z
72
+ .array(z.string().min(1))
73
+ .optional()
74
+ .describe(
75
+ 'Optional list of NocoBase knowledge base IDs to search. If omitted, all accessible KBs are searched.',
76
+ ),
77
+ topK: z.number().int().min(1).max(20).optional().describe('Maximum results to return. Default 5, max 20.'),
78
+ scoreThreshold: z
79
+ .number()
80
+ .min(0)
81
+ .max(1)
82
+ .optional()
83
+ .describe('Minimum relevance score. Default is controlled by plugin-knowledge-base.'),
84
+ }),
85
+ },
86
+
87
+ invoke: async (
88
+ ctx: any,
89
+ args: { query?: string; knowledgeBaseIds?: string[]; topK?: number; scoreThreshold?: number },
90
+ ) => {
91
+ const query = typeof args?.query === 'string' ? args.query.trim() : '';
92
+ if (!query) {
93
+ return { status: 'error' as const, content: 'Missing required field: query.' };
94
+ }
95
+
96
+ const kbPlugin = ctx?.app?.pm?.get?.('plugin-knowledge-base');
97
+ if (!kbPlugin?.searchKnowledgeBases) {
98
+ return {
99
+ status: 'error' as const,
100
+ content: 'plugin-knowledge-base is not installed or does not expose searchKnowledgeBases().',
101
+ };
102
+ }
103
+
104
+ try {
105
+ const results = await kbPlugin.searchKnowledgeBases(ctx, query, {
106
+ knowledgeBaseIds: Array.isArray(args.knowledgeBaseIds) ? args.knowledgeBaseIds.map(String) : undefined,
107
+ topK: args.topK,
108
+ scoreThreshold: args.scoreThreshold,
109
+ rerank: true,
110
+ });
111
+
112
+ return {
113
+ status: 'success' as const,
114
+ content: JSON.stringify({
115
+ query,
116
+ results: (results ?? []).map(normalizeResult),
117
+ }),
118
+ };
119
+ } catch (error: any) {
120
+ plugin.app.log?.error?.('[AgentOrchestrator] external_rag_search failed', error);
121
+ return {
122
+ status: 'error' as const,
123
+ content: `External RAG search failed: ${error?.message || String(error)}`,
124
+ };
125
+ }
126
+ },
127
+ };
128
+ }
@@ -42,8 +42,7 @@ IMPORTANT: If the skill returns file download URLs, you MUST format them as clic
42
42
  },
43
43
  },
44
44
 
45
- async invoke(args: Record<string, any>, options?: any) {
46
- const ctx = options?.context;
45
+ async invoke(ctx: any, args: Record<string, any>, _id?: string) {
47
46
  plugin.app.logger.info(`[skill-execute] Tool invoked with action: ${args.action}, skillName: ${args.skillName}`);
48
47
 
49
48
  // Action: list available skills
@@ -1,11 +0,0 @@
1
- import React from 'react';
2
- /**
3
- * Reusable Select component for AI Employees.
4
- * P3 FIX: Uses shared AIEmployeesContext instead of making its own API call.
5
- */
6
- export declare const AIEmployeeSelect: React.FC<{
7
- value?: string;
8
- onChange?: (value: string) => void;
9
- exclude?: string;
10
- placeholder?: string;
11
- }>;
@@ -1,30 +0,0 @@
1
- import React from 'react';
2
- interface AIEmployeeInfo {
3
- username: string;
4
- nickname: string;
5
- about?: string;
6
- skills: string[];
7
- }
8
- interface AIEmployeesContextType {
9
- employees: AIEmployeeInfo[];
10
- employeeMap: Map<string, string>;
11
- skillsMap: Map<string, Set<string>>;
12
- loading: boolean;
13
- refresh: () => void;
14
- }
15
- /**
16
- * P3 FIX: Shared context provider that fetches aiEmployees once
17
- * and shares the data across RulesTab, TracingTab, and AIEmployeeSelect.
18
- *
19
- * Also exposes each employee's configured skills so RulesTab can warn when
20
- * a delegation rule exists but the leader hasn't added the corresponding
21
- * delegate_<leader>_to_<sub> tool to its skillSettings.
22
- */
23
- export declare const AIEmployeesProvider: React.FC<{
24
- children: React.ReactNode;
25
- }>;
26
- /**
27
- * Hook to access shared AI employees data.
28
- */
29
- export declare const useAIEmployees: () => AIEmployeesContextType;
30
- export {};
@@ -1,3 +0,0 @@
1
- import React from 'react';
2
- declare const OrchestratorSettings: React.FC;
3
- export { OrchestratorSettings };
@@ -1,2 +0,0 @@
1
- import React from 'react';
2
- export declare const RulesTab: React.FC;
@@ -1,2 +0,0 @@
1
- import React from 'react';
2
- export declare const TracingTab: React.FC;
@@ -1 +0,0 @@
1
- export { default } from './plugin';
@@ -1,6 +0,0 @@
1
- import { Plugin } from '@nocobase/client';
2
- export declare class PluginAgentOrchestratorClient extends Plugin {
3
- load(): Promise<void>;
4
- private registerSkillUiCards;
5
- }
6
- export default PluginAgentOrchestratorClient;
@@ -1,2 +0,0 @@
1
- import React from 'react';
2
- export declare const ExecutionHistory: React.FC;