plugin-agent-orchestrator 1.0.14 → 1.0.16

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 (34) 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/CodeValidator.js +1 -0
  5. package/dist/server/services/SkillManager.js +0 -39
  6. package/dist/server/skill-hub/plugin.js +3 -3
  7. package/dist/server/skill-hub/tasks/SkillExecutionTask.d.ts +2 -0
  8. package/dist/server/skill-hub/tasks/SkillExecutionTask.js +122 -0
  9. package/dist/server/tools/delegate-task.js +22 -2
  10. package/dist/server/tools/external-rag-search.d.ts +42 -0
  11. package/dist/server/tools/external-rag-search.js +140 -0
  12. package/dist/server/tools/skill-execute.d.ts +1 -1
  13. package/dist/server/tools/skill-execute.js +1 -2
  14. package/package.json +1 -1
  15. package/src/client/index.tsx +1 -1
  16. package/src/client/plugin.tsx +54 -54
  17. package/src/client/skill-hub/index.tsx +75 -75
  18. package/src/server/migrations/20260423000000-add-progress-fields.ts +5 -5
  19. package/src/server/migrations/20260425000000-add-interaction-schema.ts +5 -5
  20. package/src/server/migrations/20260427000000-add-tracing-detail-fields.ts +5 -5
  21. package/src/server/migrations/20260427000000-change-packages-to-text.ts +7 -7
  22. package/src/server/migrations/20260427000001-change-other-json-to-text.ts +10 -10
  23. package/src/server/migrations/20260429000000-add-llm-fields.ts +2 -2
  24. package/src/server/migrations/20260429000000-fix-inputargs-json-to-text.ts +2 -2
  25. package/src/server/migrations/20260503000000-add-orchestrator-trace-fields.ts +2 -2
  26. package/src/server/plugin.ts +23 -13
  27. package/src/server/services/CodeValidator.ts +5 -5
  28. package/src/server/services/SkillManager.ts +12 -52
  29. package/src/server/services/WorkerEnvManager.ts +5 -5
  30. package/src/server/skill-hub/plugin.ts +61 -61
  31. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +162 -16
  32. package/src/server/tools/delegate-task.ts +25 -1
  33. package/src/server/tools/external-rag-search.ts +128 -0
  34. package/src/server/tools/skill-execute.ts +1 -2
package/README.md CHANGED
@@ -7,6 +7,7 @@ Hierarchical Multi-Agent orchestration for NocoBase AI Employees. Enables Leader
7
7
  - **Hierarchical Delegation**: Allows AI Leader agents to break down complex tasks and assign them to specialized sub-agents.
8
8
  - **Seamless Integration**: Plugs directly into the existing AI Employee framework.
9
9
  - **Execution Tracking**: Monitor sub-agent task execution and responses within the main chat interface.
10
+ - **External RAG Search**: Exposes `external_rag_search` so leaders and sub-agents can retrieve context from NocoBase knowledge bases, including external RAG services.
10
11
 
11
12
  ## Usage
12
13
  1. Enable the plugin in the NocoBase Plugin Manager.
@@ -14,3 +15,98 @@ Hierarchical Multi-Agent orchestration for NocoBase AI Employees. Enables Leader
14
15
  3. Configure a primary "Leader" agent.
15
16
  4. Add available "Sub-Agents" as tools or skills to the Leader agent.
16
17
  5. Interact with the Leader agent; it will automatically delegate tasks when necessary.
18
+
19
+ ## External RAG / Embedding service
20
+
21
+ Recommended approach: keep source ownership in NocoBase and move chunking, embedding, vector storage, and retrieval to an external lightweight RAG service. This plugin should not own vector infrastructure directly. It delegates search to `plugin-knowledge-base`, which already supports knowledge bases of type `EXTERNAL_RAG`.
22
+
23
+ ### Architecture
24
+
25
+ 1. NocoBase stores the source metadata:
26
+ - uploaded file attachment id, filename, storage id, URL, owner/role access, or
27
+ - datasource/collection/record id for database-backed knowledge.
28
+ 2. The external RAG service owns:
29
+ - document fetching or receiving source payloads,
30
+ - parsing/chunking,
31
+ - embedding,
32
+ - vector index,
33
+ - retrieval.
34
+ 3. NocoBase creates a knowledge base with `type = EXTERNAL_RAG` and `options` such as:
35
+ - `ragProvider: "external-http"`
36
+ - `ragApiUrl: "https://rag.example.com/search"`
37
+ - `ragApiKey`
38
+ - `ragNamespace`
39
+ - `ragTopK`
40
+ - `ragScoreThreshold`
41
+ 4. Agents use the `external_rag_search` tool. The tool calls `plugin-knowledge-base.searchKnowledgeBases()`, so access control and mixed local/external search remain centralized.
42
+
43
+ ### External search contract
44
+
45
+ The built-in `external-http` strategy expects:
46
+
47
+ ```http
48
+ POST /search
49
+ Authorization: Bearer <ragApiKey>
50
+ Content-Type: application/json
51
+ ```
52
+
53
+ ```json
54
+ {
55
+ "query": "search text",
56
+ "topK": 5,
57
+ "scoreThreshold": 0.3,
58
+ "namespace": "optional-kb-namespace",
59
+ "filter": {}
60
+ }
61
+ ```
62
+
63
+ Response:
64
+
65
+ ```json
66
+ {
67
+ "results": [
68
+ {
69
+ "id": "chunk-or-source-id",
70
+ "content": "matched text",
71
+ "score": 0.82,
72
+ "metadata": {
73
+ "fileId": "123",
74
+ "filename": "contract.pdf",
75
+ "collection": "orders",
76
+ "recordId": "456",
77
+ "sourceUrl": "/api/attachments/123:download"
78
+ }
79
+ }
80
+ ]
81
+ }
82
+ ```
83
+
84
+ The important rule is that every result should return enough metadata for NocoBase to resolve the original source: `fileId`/`filename` for files, or `collection`/`recordId` for datasource records.
85
+
86
+ ### Lightweight open-source service target
87
+
88
+ For a small but useful deployment, use a standalone service with:
89
+
90
+ - Qdrant or LanceDB for vector search.
91
+ - FastAPI or Express for `/ingest`, `/delete`, and `/search`.
92
+ - A small embedding model such as `BAAI/bge-small-en-v1.5`, `intfloat/multilingual-e5-small`, or another model matching the deployment language.
93
+ - A namespace per NocoBase knowledge base.
94
+
95
+ Minimum API surface:
96
+
97
+ - `POST /ingest`: receive `{ namespace, source, content | fileUrl | storageRef, metadata }`.
98
+ - `POST /delete`: receive `{ namespace, sourceId }`.
99
+ - `POST /search`: implement the contract above.
100
+
101
+ For NocoBase datasource knowledge, send each record as a source document with metadata `{ collection, recordId, fields, updatedAt }`; the RAG service chunks and embeds the selected text fields, then returns `collection` and `recordId` in search results.
102
+
103
+ ### E5/OpenAI-compatible embedding
104
+
105
+ If the external RAG service uses an E5 family model behind an OpenAI-compatible `/v1/embeddings` API, configure the NocoBase knowledge base with `ragProvider: "e5-http"`. `plugin-knowledge-base` will read the selected embedding LLM service and forward its `baseURL`, `apiKey`, and model to the RAG service.
106
+
107
+ - embed user queries as `query: <question>`;
108
+ - embed document chunks as `passage: <chunk>`;
109
+ - use the same model for ingest and search;
110
+ - recreate the vector collection when changing models or vector dimensions.
111
+
112
+ The RAG service still owns chunking, embedding calls, vector storage, and metadata mapping. NocoBase owns the LLM service configuration and search authorization path.
@@ -9,14 +9,14 @@
9
9
 
10
10
  module.exports = {
11
11
  "react": "18.2.0",
12
- "@nocobase/client": "2.0.46",
12
+ "@nocobase/client": "2.0.49",
13
13
  "antd": "5.24.2",
14
14
  "@ant-design/icons": "5.6.1",
15
- "@nocobase/server": "2.0.46",
16
- "@nocobase/database": "2.0.46",
17
- "@nocobase/actions": "2.0.46",
15
+ "@nocobase/server": "2.0.49",
16
+ "@nocobase/database": "2.0.49",
17
+ "@nocobase/actions": "2.0.49",
18
18
  "@langchain/langgraph": "0.2.74",
19
19
  "@langchain/core": "0.3.80",
20
- "@nocobase/plugin-ai": "2.0.46",
21
- "@nocobase/ai": "2.0.46"
20
+ "@nocobase/plugin-ai": "2.0.49",
21
+ "@nocobase/ai": "2.0.49"
22
22
  };
@@ -43,6 +43,7 @@ module.exports = __toCommonJS(plugin_exports);
43
43
  var import_server = require("@nocobase/server");
44
44
  var import_path = __toESM(require("path"));
45
45
  var import_delegate_task = require("./tools/delegate-task");
46
+ var import_external_rag_search = require("./tools/external-rag-search");
46
47
  var import_tracing = require("./resources/tracing");
47
48
  var import_plugin = __toESM(require("./skill-hub/plugin"));
48
49
  class PluginAgentOrchestratorServer extends import_server.Plugin {
@@ -62,9 +63,18 @@ class PluginAgentOrchestratorServer extends import_server.Plugin {
62
63
  await this.skillHub.load();
63
64
  this.app.acl.registerSnippet({
64
65
  name: `pm.${this.name}`,
65
- actions: ["orchestratorConfig:*", "orchestratorTracing:*", "agentExecutionSpans:*", "skillDefinitions:*", "skillExecutions:*", "skillHub:*", "skillWorkerConfigs:*"]
66
+ actions: [
67
+ "orchestratorConfig:*",
68
+ "orchestratorTracing:*",
69
+ "agentExecutionSpans:*",
70
+ "skillDefinitions:*",
71
+ "skillExecutions:*",
72
+ "skillHub:*",
73
+ "skillWorkerConfigs:*"
74
+ ]
66
75
  });
67
76
  const toolsManager = this.app.aiManager.toolsManager;
77
+ toolsManager.registerTools((0, import_external_rag_search.createExternalRagSearchTool)(this));
68
78
  toolsManager.registerDynamicTools((0, import_delegate_task.createDelegateToolsProvider)(this));
69
79
  (0, import_tracing.registerTracingResource)(this);
70
80
  this.app.cronJobManager.addJob({
@@ -111,6 +111,7 @@ const PYTHON_BUILTINS = [
111
111
  "logging",
112
112
  "unittest",
113
113
  "argparse",
114
+ "ast",
114
115
  "tempfile",
115
116
  "xml",
116
117
  "zipfile",
@@ -347,45 +347,6 @@ if table_data_raw and table_data_raw != '{{' + 'tableData}}':
347
347
  doc.build(story)
348
348
  print('Generated: report.pdf')
349
349
  `;
350
- const SEED_PPTX = `import os, json
351
- from pptx import Presentation
352
- from pptx.util import Inches, Pt
353
- from pptx.enum.text import PP_ALIGN
354
-
355
- title = json.loads('''{{title}}''') if '{{title}}'.startswith('"') else '{{title}}'
356
- subtitle_raw = '''{{subtitle}}'''
357
- subtitle = json.loads(subtitle_raw) if subtitle_raw.startswith('"') else subtitle_raw if subtitle_raw != '{{' + 'subtitle}}' else ''
358
- slides_data = json.loads('''{{slides}}''')
359
-
360
- prs = Presentation()
361
- prs.slide_width = Inches(13.333)
362
- prs.slide_height = Inches(7.5)
363
-
364
- # Title slide
365
- slide = prs.slides.add_slide(prs.slide_layouts[0])
366
- slide.shapes.title.text = title
367
- if subtitle and slide.placeholders[1]:
368
- slide.placeholders[1].text = subtitle
369
-
370
- # Content slides
371
- for s in slides_data:
372
- slide = prs.slides.add_slide(prs.slide_layouts[1])
373
- slide.shapes.title.text = s.get('title', '')
374
- body = slide.placeholders[1].text_frame
375
- body.clear()
376
- for i, bullet in enumerate(s.get('bullets', [])):
377
- if i == 0:
378
- body.paragraphs[0].text = bullet
379
- else:
380
- p = body.add_paragraph()
381
- p.text = bullet
382
- body.paragraphs[-1].font.size = Pt(18)
383
-
384
- output_dir = os.environ.get('OUTPUT_DIR', '/output')
385
- filepath = os.path.join(output_dir, 'presentation.pptx')
386
- prs.save(filepath)
387
- print('Generated: presentation.pptx')
388
- `;
389
350
  const SEED_CHART = `import os, json
390
351
  import matplotlib
391
352
  matplotlib.use('Agg')
@@ -498,7 +498,7 @@ IMPORTANT: This skill requires human confirmation. Pass best-effort args; the us
498
498
  filter: { id: skill.get("id"), enabled: true }
499
499
  });
500
500
  if (!latestSkill) {
501
- return { error: `Skill "${skill.get("name")}" is no longer available` };
501
+ return { status: "error", content: `Skill "${skill.get("name")}" is no longer available` };
502
502
  }
503
503
  const result = await this.executeSkill(latestSkill, args, toolCtx);
504
504
  return {
@@ -530,7 +530,7 @@ IMPORTANT: This skill requires human confirmation. Pass best-effort args; the us
530
530
  const cutoff = new Date(Date.now() - MAX_AGE_MS);
531
531
  const repo = this.db.getRepository("skillExecutions");
532
532
  const outdated = await repo.find({
533
- where: { createdAt: { $lt: cutoff } }
533
+ filter: { createdAt: { $lt: cutoff } }
534
534
  });
535
535
  if (outdated.length > 0) {
536
536
  for (const record of outdated) {
@@ -579,7 +579,7 @@ IMPORTANT: This skill requires human confirmation. Pass best-effort args; the us
579
579
  const hours = config ? config.get("retentionHours") : 24;
580
580
  if (hours > 0) {
581
581
  const cutoff = new Date(Date.now() - hours * 60 * 60 * 1e3);
582
- const results = await repo.find({ where: { createdAt: { $lt: cutoff } }, fields: ["id"] });
582
+ const results = await repo.find({ filter: { createdAt: { $lt: cutoff } }, fields: ["id"] });
583
583
  for (const rec of results) {
584
584
  await rec.destroy();
585
585
  }
@@ -11,4 +11,6 @@ export declare class SkillExecutionTask {
11
11
  constructor(execution: any, sandboxRunner: SandboxRunner, fileManager: FileManager, skillRepoService: SkillRepositoryService, app: Application);
12
12
  run(): Promise<void>;
13
13
  private renderTemplate;
14
+ private installGeneratedSkillIfRequested;
15
+ private validateGeneratedSkillPackages;
14
16
  }
@@ -29,7 +29,9 @@ __export(SkillExecutionTask_exports, {
29
29
  SkillExecutionTask: () => SkillExecutionTask
30
30
  });
31
31
  module.exports = __toCommonJS(SkillExecutionTask_exports);
32
+ var import_fs = require("fs");
32
33
  var import_path = require("path");
34
+ var import_CodeValidator = require("../../services/CodeValidator");
33
35
  var import_json_fields = require("../utils/json-fields");
34
36
  class TaskAbortController {
35
37
  listeners = [];
@@ -203,6 +205,12 @@ class SkillExecutionTask {
203
205
  this.app.pubSubManager.publish(`skill-hub.progress.${execId}`, progress);
204
206
  }
205
207
  });
208
+ if (result.success) {
209
+ const installMessage = await this.installGeneratedSkillIfRequested(execId);
210
+ if (installMessage) {
211
+ result.stdout = [result.stdout, installMessage].filter(Boolean).join("\n");
212
+ }
213
+ }
206
214
  let status;
207
215
  if (result.canceled) {
208
216
  status = "canceled";
@@ -260,6 +268,120 @@ class SkillExecutionTask {
260
268
  code = code.replaceAll("{{skillDir}}", (skillDir || "").replace(/\\/g, "/"));
261
269
  return code;
262
270
  }
271
+ async installGeneratedSkillIfRequested(execId) {
272
+ const manifestPath = this.fileManager.getOutputFilePath(execId, "skill-hub-install.json");
273
+ if (!manifestPath) return null;
274
+ let manifest;
275
+ try {
276
+ manifest = JSON.parse((0, import_fs.readFileSync)(manifestPath, "utf8"));
277
+ } catch (error) {
278
+ throw new Error(`Generated skill install manifest is invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
279
+ }
280
+ if (!(manifest == null ? void 0 : manifest.autoInstall)) return null;
281
+ const skill = manifest.skill || {};
282
+ const name = String(skill.name || "").trim();
283
+ if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(name)) {
284
+ throw new Error(`Generated skill name "${name}" is invalid. Use lowercase letters, numbers, and hyphens.`);
285
+ }
286
+ const language = skill.language;
287
+ if (language !== "python" && language !== "node") {
288
+ throw new Error(`Generated skill "${name}" has unsupported language "${language}".`);
289
+ }
290
+ if (!skill.codeTemplate) {
291
+ throw new Error(`Generated skill "${name}" is missing codeTemplate.`);
292
+ }
293
+ const validator = new import_CodeValidator.CodeValidator();
294
+ validator.validate(skill.codeTemplate, language);
295
+ await this.validateGeneratedSkillPackages(name, language, skill.packages);
296
+ const outputDir = this.fileManager.getOutputDir(execId);
297
+ const outputRoot = (0, import_path.resolve)(outputDir);
298
+ const packageDirName = String(manifest.packageDir || name);
299
+ const packageDir = (0, import_path.resolve)(outputRoot, packageDirName);
300
+ if (packageDir !== outputRoot && !packageDir.startsWith(outputRoot + import_path.sep) || !(0, import_fs.existsSync)(packageDir)) {
301
+ throw new Error(`Generated skill package directory "${packageDirName}" was not found in output.`);
302
+ }
303
+ if (manifest.testInput && Object.keys(manifest.testInput).length > 0) {
304
+ const verifyExecId = `${execId}-verify-${name}`;
305
+ const verifyCode = this.renderTemplate(skill.codeTemplate, manifest.testInput, verifyExecId, packageDir);
306
+ const verifyResult = await this.sandboxRunner.execute({
307
+ language,
308
+ code: verifyCode,
309
+ execId: verifyExecId,
310
+ timeoutSeconds: Math.min(Number(skill.timeoutSeconds || 60), 30),
311
+ maxOutputSizeMb: Math.min(Number(skill.maxOutputSizeMb || 50), 10),
312
+ skillDir: packageDir
313
+ });
314
+ if (!verifyResult.success) {
315
+ throw new Error(
316
+ `Generated skill "${name}" failed smoke verification: ${verifyResult.stderr || verifyResult.stdout || "unknown error"}`
317
+ );
318
+ }
319
+ }
320
+ const skillRepoDir = this.skillRepoService.getSkillPath(name);
321
+ if ((0, import_fs.existsSync)(skillRepoDir)) {
322
+ (0, import_fs.rmSync)(skillRepoDir, { recursive: true, force: true });
323
+ }
324
+ (0, import_fs.cpSync)(packageDir, skillRepoDir, {
325
+ recursive: true,
326
+ force: true,
327
+ filter: (src) => {
328
+ const leaf = src.split(/[\\/]/).pop();
329
+ return !["node_modules", ".git", "__pycache__"].includes(leaf || "") && !src.endsWith(".pyc");
330
+ }
331
+ });
332
+ const values = {
333
+ name,
334
+ title: skill.title || name,
335
+ description: skill.description || "",
336
+ instructions: skill.instructions || "",
337
+ language,
338
+ codeTemplate: skill.codeTemplate,
339
+ inputSchema: (0, import_json_fields.stringifyJsonText)(skill.inputSchema || { type: "object", properties: {} }),
340
+ packages: (0, import_json_fields.stringifyJsonText)(skill.packages || [], []),
341
+ timeoutSeconds: skill.timeoutSeconds || 60,
342
+ maxOutputSizeMb: skill.maxOutputSizeMb || 50,
343
+ enabled: skill.enabled !== false,
344
+ toolScope: skill.toolScope || "CUSTOM",
345
+ autoCall: !!skill.autoCall,
346
+ storageType: "local",
347
+ storageUrl: `local://generated/${name}`
348
+ };
349
+ if (skill.interactionSchema) {
350
+ values.interactionSchema = (0, import_json_fields.stringifyJsonText)(skill.interactionSchema);
351
+ }
352
+ const repo = this.app.db.getRepository("skillDefinitions");
353
+ const existing = await repo.findOne({ filter: { name } });
354
+ if (existing) {
355
+ if (manifest.overwrite === false) {
356
+ throw new Error(`Skill "${name}" already exists and overwrite=false.`);
357
+ }
358
+ await repo.update({ filter: { name }, values });
359
+ return `[skill-hub] Updated generated skill "${name}" in Skill Hub.`;
360
+ }
361
+ await repo.create({ values });
362
+ return `[skill-hub] Installed generated skill "${name}" in Skill Hub.`;
363
+ }
364
+ async validateGeneratedSkillPackages(name, language, packages) {
365
+ if (!Array.isArray(packages) || packages.length === 0) return;
366
+ if (!packages.every((pkg) => typeof pkg === "string" && pkg.trim())) {
367
+ throw new Error(`Generated skill "${name}" has invalid packages. Use an array of package names.`);
368
+ }
369
+ const workerConfig = await this.app.db.getRepository("skillWorkerConfigs").findOne();
370
+ if (!workerConfig) return;
371
+ const whitelist = (0, import_json_fields.parseJsonText)(
372
+ workerConfig.get ? workerConfig.get("packageWhitelist") : workerConfig.packageWhitelist,
373
+ null
374
+ );
375
+ const allowed = whitelist == null ? void 0 : whitelist[language];
376
+ if (!Array.isArray(allowed) || allowed.length === 0) return;
377
+ const allowedSet = new Set(allowed.map((pkg) => pkg.toLowerCase()));
378
+ const missing = packages.map((pkg) => pkg.trim()).filter((pkg) => !allowedSet.has(pkg.toLowerCase()));
379
+ if (missing.length > 0) {
380
+ throw new Error(
381
+ `Generated skill "${name}" requires ${language} package(s) not available in the Skill Hub worker environment: ${missing.join(", ")}. Add them to the worker environment and refresh/init Skill Hub before installing this skill.`
382
+ );
383
+ }
384
+ }
263
385
  }
264
386
  // Annotate the CommonJS export names for ESM import in node:
265
387
  0 && (module.exports = {
@@ -554,7 +554,7 @@ function invalidateDelegateToolsCache() {
554
554
  registeredDelegateNamesByPlugin = /* @__PURE__ */ new WeakMap();
555
555
  }
556
556
  async function invokeDelegateTask(ctx, plugin, options) {
557
- var _a, _b, _c, _d, _e;
557
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
558
558
  const {
559
559
  leaderUsername,
560
560
  subAgentUsername,
@@ -771,7 +771,27 @@ async function invokeDelegateTask(ctx, plugin, options) {
771
771
  llm: chatModel,
772
772
  tools: langchainTools
773
773
  });
774
- const systemPrompt = ((_e = subAgentEmployee.chatSettings) == null ? void 0 : _e.systemPrompt) || subAgentEmployee.bio || `You are an AI assistant named "${subAgentEmployee.nickname || subAgentUsername}". ${subAgentEmployee.about || ""}`;
774
+ let systemPrompt = ((_e = subAgentEmployee.chatSettings) == null ? void 0 : _e.systemPrompt) || subAgentEmployee.bio || `You are an AI assistant named "${subAgentEmployee.nickname || subAgentUsername}". ${subAgentEmployee.about || ""}`;
775
+ try {
776
+ const kbPlugin = ctx.app.pm.get("plugin-knowledge-base");
777
+ if (kbPlugin == null ? void 0 : kbPlugin.sessionContext) {
778
+ const sessionId = ((_h = (_g = (_f = ctx.action) == null ? void 0 : _f.params) == null ? void 0 : _g.values) == null ? void 0 : _h.sessionId) || ((_j = (_i = ctx.action) == null ? void 0 : _i.params) == null ? void 0 : _j.sessionId) || ((_k = ctx.state) == null ? void 0 : _k.sessionId);
779
+ const contextSummary = await kbPlugin.sessionContext.buildSummary(
780
+ { rootRunId, ...sessionId ? { sessionId } : {} },
781
+ 6e3
782
+ );
783
+ if (contextSummary) {
784
+ systemPrompt += `
785
+
786
+ <shared_context>
787
+ The following context was shared by other agents in this workflow. Use it to avoid redundant work:
788
+ ${contextSummary}
789
+ </shared_context>`;
790
+ }
791
+ }
792
+ } catch (e) {
793
+ (_m = (_l = ctx.app.log) == null ? void 0 : _l.debug) == null ? void 0 : _m.call(_l, `[AgentOrchestrator] Shared context injection skipped: ${e.message}`);
794
+ }
775
795
  const combinedTask = context ? `Task: ${task}
776
796
 
777
797
  Context Provided:
@@ -0,0 +1,42 @@
1
+ import { z } from 'zod';
2
+ export declare function createExternalRagSearchTool(plugin: any): {
3
+ scope: "CUSTOM";
4
+ execution: "backend";
5
+ defaultPermission: "ALLOW";
6
+ introduction: {
7
+ title: string;
8
+ about: string;
9
+ };
10
+ definition: {
11
+ name: string;
12
+ description: string;
13
+ schema: z.ZodObject<{
14
+ query: z.ZodString;
15
+ knowledgeBaseIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
16
+ topK: z.ZodOptional<z.ZodNumber>;
17
+ scoreThreshold: z.ZodOptional<z.ZodNumber>;
18
+ }, "strip", z.ZodTypeAny, {
19
+ query?: string;
20
+ knowledgeBaseIds?: string[];
21
+ topK?: number;
22
+ scoreThreshold?: number;
23
+ }, {
24
+ query?: string;
25
+ knowledgeBaseIds?: string[];
26
+ topK?: number;
27
+ scoreThreshold?: number;
28
+ }>;
29
+ };
30
+ invoke: (ctx: any, args: {
31
+ query?: string;
32
+ knowledgeBaseIds?: string[];
33
+ topK?: number;
34
+ scoreThreshold?: number;
35
+ }) => Promise<{
36
+ status: "error";
37
+ content: string;
38
+ } | {
39
+ status: "success";
40
+ content: string;
41
+ }>;
42
+ };
@@ -0,0 +1,140 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ var __defProp = Object.defineProperty;
11
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
12
+ var __getOwnPropNames = Object.getOwnPropertyNames;
13
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
+ var external_rag_search_exports = {};
28
+ __export(external_rag_search_exports, {
29
+ createExternalRagSearchTool: () => createExternalRagSearchTool
30
+ });
31
+ module.exports = __toCommonJS(external_rag_search_exports);
32
+ var import_zod = require("zod");
33
+ const TOOL_NAME = "external_rag_search";
34
+ const MAX_CONTENT_LENGTH = 4e3;
35
+ function truncate(value, max = MAX_CONTENT_LENGTH) {
36
+ const text = typeof value === "string" ? value : value == null ? "" : String(value);
37
+ return text.length > max ? `${text.slice(0, max)}...` : text;
38
+ }
39
+ function firstString(...values) {
40
+ for (const value of values) {
41
+ if (typeof value === "string" && value.trim()) {
42
+ return value.trim();
43
+ }
44
+ if (typeof value === "number" && Number.isFinite(value)) {
45
+ return String(value);
46
+ }
47
+ }
48
+ return void 0;
49
+ }
50
+ function normalizeResult(result) {
51
+ const metadata = (result == null ? void 0 : result.metadata) ?? {};
52
+ const sourceId = firstString(
53
+ result == null ? void 0 : result.id,
54
+ metadata.id,
55
+ metadata.sourceId,
56
+ metadata.documentId,
57
+ metadata.docId,
58
+ metadata.fileId,
59
+ metadata.recordId
60
+ );
61
+ const filename = firstString(metadata.filename, metadata.fileName, metadata.name, metadata.title, metadata.source);
62
+ return {
63
+ content: truncate(result == null ? void 0 : result.content),
64
+ score: Number((result == null ? void 0 : result.rerankScore) ?? (result == null ? void 0 : result.score) ?? (result == null ? void 0 : result.vectorScore) ?? 0),
65
+ knowledgeBaseId: result == null ? void 0 : result.knowledgeBaseId,
66
+ knowledgeBaseName: result == null ? void 0 : result.knowledgeBaseName,
67
+ source: {
68
+ id: sourceId,
69
+ filename,
70
+ url: firstString(metadata.url, metadata.fileUrl, metadata.sourceUrl),
71
+ collection: firstString(metadata.collection, metadata.collectionName),
72
+ recordId: firstString(metadata.recordId, metadata.rowId)
73
+ },
74
+ metadata
75
+ };
76
+ }
77
+ function createExternalRagSearchTool(plugin) {
78
+ return {
79
+ scope: "CUSTOM",
80
+ execution: "backend",
81
+ defaultPermission: "ALLOW",
82
+ introduction: {
83
+ title: "External RAG Search",
84
+ about: "Search NocoBase knowledge bases through plugin-knowledge-base, including EXTERNAL_RAG services that own chunking, embedding, and retrieval."
85
+ },
86
+ definition: {
87
+ name: TOOL_NAME,
88
+ description: `Search configured knowledge bases for relevant context. Use this before answering questions that require documents, files, or datasource-backed knowledge.
89
+
90
+ 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.`,
91
+ schema: import_zod.z.object({
92
+ query: import_zod.z.string().min(1).describe("Natural-language search query."),
93
+ knowledgeBaseIds: import_zod.z.array(import_zod.z.string().min(1)).optional().describe(
94
+ "Optional list of NocoBase knowledge base IDs to search. If omitted, all accessible KBs are searched."
95
+ ),
96
+ topK: import_zod.z.number().int().min(1).max(20).optional().describe("Maximum results to return. Default 5, max 20."),
97
+ scoreThreshold: import_zod.z.number().min(0).max(1).optional().describe("Minimum relevance score. Default is controlled by plugin-knowledge-base.")
98
+ })
99
+ },
100
+ invoke: async (ctx, args) => {
101
+ var _a, _b, _c, _d, _e;
102
+ const query = typeof (args == null ? void 0 : args.query) === "string" ? args.query.trim() : "";
103
+ if (!query) {
104
+ return { status: "error", content: "Missing required field: query." };
105
+ }
106
+ const kbPlugin = (_c = (_b = (_a = ctx == null ? void 0 : ctx.app) == null ? void 0 : _a.pm) == null ? void 0 : _b.get) == null ? void 0 : _c.call(_b, "plugin-knowledge-base");
107
+ if (!(kbPlugin == null ? void 0 : kbPlugin.searchKnowledgeBases)) {
108
+ return {
109
+ status: "error",
110
+ content: "plugin-knowledge-base is not installed or does not expose searchKnowledgeBases()."
111
+ };
112
+ }
113
+ try {
114
+ const results = await kbPlugin.searchKnowledgeBases(ctx, query, {
115
+ knowledgeBaseIds: Array.isArray(args.knowledgeBaseIds) ? args.knowledgeBaseIds.map(String) : void 0,
116
+ topK: args.topK,
117
+ scoreThreshold: args.scoreThreshold,
118
+ rerank: true
119
+ });
120
+ return {
121
+ status: "success",
122
+ content: JSON.stringify({
123
+ query,
124
+ results: (results ?? []).map(normalizeResult)
125
+ })
126
+ };
127
+ } catch (error) {
128
+ (_e = (_d = plugin.app.log) == null ? void 0 : _d.error) == null ? void 0 : _e.call(_d, "[AgentOrchestrator] external_rag_search failed", error);
129
+ return {
130
+ status: "error",
131
+ content: `External RAG search failed: ${(error == null ? void 0 : error.message) || String(error)}`
132
+ };
133
+ }
134
+ }
135
+ };
136
+ }
137
+ // Annotate the CommonJS export names for ESM import in node:
138
+ 0 && (module.exports = {
139
+ createExternalRagSearchTool
140
+ });
@@ -29,7 +29,7 @@ export declare function createSkillExecuteTool(plugin: any): {
29
29
  required: string[];
30
30
  };
31
31
  };
32
- invoke(args: Record<string, any>, options?: any): Promise<{
32
+ invoke(ctx: any, args: Record<string, any>, _id?: string): Promise<{
33
33
  status: string;
34
34
  content: string;
35
35
  }>;
@@ -69,9 +69,8 @@ IMPORTANT: If the skill returns file download URLs, you MUST format them as clic
69
69
  required: ["action"]
70
70
  }
71
71
  },
72
- async invoke(args, options) {
72
+ async invoke(ctx, args, _id) {
73
73
  var _a;
74
- const ctx = options == null ? void 0 : options.context;
75
74
  plugin.app.logger.info(`[skill-execute] Tool invoked with action: ${args.action}, skillName: ${args.skillName}`);
76
75
  if (args.action === "list") {
77
76
  const skills = await plugin.db.getRepository("skillDefinitions").find({
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "displayName.zh-CN": "代理协调器",
5
5
  "displayName.vi-VN": "Điều phối Agent",
6
6
  "description": "Hierarchical Multi-Agent orchestration for NocoBase AI Employees. Enables Leader agents to delegate tasks to Sub-Agent employees without modifying core plugin-ai.",
7
- "version": "1.0.14",
7
+ "version": "1.0.16",
8
8
  "license": "Apache-2.0",
9
9
  "main": "dist/server/index.js",
10
10
  "keywords": [
@@ -1 +1 @@
1
- export { default } from './plugin';
1
+ export { default } from './plugin';