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.
- package/README.md +96 -0
- package/dist/externalVersion.js +6 -6
- package/dist/server/plugin.js +11 -1
- package/dist/server/services/SkillManager.js +0 -39
- package/dist/server/skill-hub/plugin.js +3 -3
- package/dist/server/tools/delegate-task.js +60 -4
- package/dist/server/tools/external-rag-search.js +140 -0
- package/dist/server/tools/skill-execute.js +1 -2
- package/package.json +2 -2
- package/src/server/plugin.ts +104 -94
- package/src/server/services/SkillManager.ts +13 -53
- package/src/server/skill-hub/plugin.ts +3 -3
- package/src/server/tools/delegate-task.ts +77 -6
- package/src/server/tools/external-rag-search.ts +128 -0
- package/src/server/tools/skill-execute.ts +1 -2
- package/dist/client/AIEmployeeSelect.d.ts +0 -11
- package/dist/client/AIEmployeesContext.d.ts +0 -30
- package/dist/client/OrchestratorSettings.d.ts +0 -3
- package/dist/client/RulesTab.d.ts +0 -2
- package/dist/client/TracingTab.d.ts +0 -2
- package/dist/client/index.d.ts +0 -1
- package/dist/client/plugin.d.ts +0 -6
- package/dist/client/skill-hub/components/ExecutionHistory.d.ts +0 -2
- package/dist/client/skill-hub/components/ExecutionProgress.d.ts +0 -20
- package/dist/client/skill-hub/components/GitSkillImport.d.ts +0 -7
- package/dist/client/skill-hub/components/SkillEditor.d.ts +0 -7
- package/dist/client/skill-hub/components/SkillManager.d.ts +0 -2
- package/dist/client/skill-hub/components/SkillMetrics.d.ts +0 -2
- package/dist/client/skill-hub/components/SkillTestPanel.d.ts +0 -7
- package/dist/client/skill-hub/index.d.ts +0 -10
- package/dist/client/skill-hub/locale.d.ts +0 -3
- package/dist/client/skill-hub/tools/InteractionSchemasProvider.d.ts +0 -19
- package/dist/client/skill-hub/tools/SkillHubCard.d.ts +0 -3
- package/dist/client/skill-hub/utils/jsonFields.d.ts +0 -3
- package/dist/index.d.ts +0 -2
- package/dist/server/collections/agent-execution-spans.d.ts +0 -9
- package/dist/server/collections/orchestrator-config.d.ts +0 -2
- package/dist/server/collections/orchestrator-logs.d.ts +0 -8
- package/dist/server/collections/skill-definitions.d.ts +0 -2
- package/dist/server/collections/skill-executions.d.ts +0 -2
- package/dist/server/collections/skill-worker-configs.d.ts +0 -2
- package/dist/server/index.d.ts +0 -1
- package/dist/server/migrations/20260423000000-add-progress-fields.d.ts +0 -4
- package/dist/server/migrations/20260425000000-add-interaction-schema.d.ts +0 -4
- package/dist/server/migrations/20260427000000-add-tracing-detail-fields.d.ts +0 -7
- package/dist/server/migrations/20260427000000-change-packages-to-text.d.ts +0 -4
- package/dist/server/migrations/20260427000001-change-other-json-to-text.d.ts +0 -4
- package/dist/server/migrations/20260429000000-add-llm-fields.d.ts +0 -7
- package/dist/server/migrations/20260429000000-fix-inputargs-json-to-text.d.ts +0 -16
- package/dist/server/migrations/20260503000000-add-orchestrator-trace-fields.d.ts +0 -7
- package/dist/server/plugin.d.ts +0 -14
- package/dist/server/resources/tracing.d.ts +0 -7
- package/dist/server/services/CodeValidator.d.ts +0 -32
- package/dist/server/services/ExecutionSpanService.d.ts +0 -44
- package/dist/server/services/FileManager.d.ts +0 -28
- package/dist/server/services/SandboxRunner.d.ts +0 -41
- package/dist/server/services/SkillManager.d.ts +0 -6
- package/dist/server/services/SkillRepositoryService.d.ts +0 -22
- package/dist/server/services/WorkerEnvManager.d.ts +0 -26
- package/dist/server/skill-hub/actions/git-import.d.ts +0 -21
- package/dist/server/skill-hub/mcp/McpController.d.ts +0 -15
- package/dist/server/skill-hub/plugin.d.ts +0 -58
- package/dist/server/skill-hub/tasks/SkillExecutionTask.d.ts +0 -16
- package/dist/server/skill-hub/utils/json-fields.d.ts +0 -7
- package/dist/server/tools/delegate-task.d.ts +0 -19
- package/dist/server/tools/skill-execute.d.ts +0 -36
package/src/server/plugin.ts
CHANGED
|
@@ -1,94 +1,104 @@
|
|
|
1
|
-
import { Plugin } from '@nocobase/server';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { createDelegateToolsProvider } from './tools/delegate-task';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
(this
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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({
|
|
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
|
-
|
|
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:
|
|
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>,
|
|
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 {};
|
package/dist/client/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { default } from './plugin';
|
package/dist/client/plugin.d.ts
DELETED