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
@@ -38,19 +38,47 @@ const DANGEROUS_NODE_PATTERNS = [
38
38
  { pattern: /require\s*\(\s*['"]http['"]\s*\)/, reason: "http module not allowed" },
39
39
  { pattern: /require\s*\(\s*['"]https['"]\s*\)/, reason: "https module not allowed" },
40
40
  { pattern: /require\s*\(\s*['"]vm['"]\s*\)/, reason: "vm module not allowed" },
41
+ { pattern: /require\s*\(\s*['"]worker_threads['"]\s*\)/, reason: "worker_threads module not allowed" },
42
+ { pattern: /require\s*\(\s*['"]inspector['"]\s*\)/, reason: "inspector module not allowed" },
43
+ { pattern: /require\s*\(\s*['"]v8['"]\s*\)/, reason: "v8 module not allowed" },
41
44
  { pattern: /process\.exit/, reason: "process.exit not allowed" },
42
45
  { pattern: /process\.env(?!\s*\.OUTPUT_DIR)/, reason: "process.env access not allowed (use OUTPUT_DIR only)" },
43
- { pattern: /process\.kill/, reason: "process.kill not allowed" }
46
+ { pattern: /process\.kill/, reason: "process.kill not allowed" },
47
+ { pattern: /process\.binding\s*\(/, reason: "process.binding not allowed" },
48
+ { pattern: /\bglobalThis\b/, reason: "globalThis access not allowed" },
49
+ // Indirect eval / dynamic Function construction (bypasses the import allowlist).
50
+ { pattern: /\beval\s*\(/, reason: "eval not allowed" },
51
+ { pattern: /\bnew\s+Function\s*\(/, reason: "new Function() not allowed" },
52
+ { pattern: /\bFunction\s*\(/, reason: "Function() constructor not allowed" },
53
+ // Dynamic import() can pull arbitrary modules at runtime, bypassing require() checks.
54
+ { pattern: /\bimport\s*\(/, reason: "dynamic import() not allowed" },
55
+ // require resolved from a non-literal expression (e.g. require(varName)).
56
+ { pattern: /require\s*\(\s*(?!['"])/, reason: "require() with a non-literal argument not allowed" }
44
57
  ];
45
58
  const DANGEROUS_PYTHON_PATTERNS = [
46
59
  { pattern: /import\s+subprocess/, reason: "subprocess module not allowed" },
47
60
  { pattern: /from\s+subprocess\s+import/, reason: "subprocess module not allowed" },
48
61
  { pattern: /import\s+shutil/, reason: "shutil module not allowed" },
62
+ { pattern: /import\s+socket/, reason: "socket module not allowed" },
63
+ { pattern: /import\s+ctypes/, reason: "ctypes module not allowed" },
64
+ { pattern: /import\s+pickle/, reason: "pickle module not allowed" },
65
+ { pattern: /import\s+marshal/, reason: "marshal module not allowed" },
66
+ { pattern: /import\s+importlib/, reason: "importlib module not allowed" },
67
+ {
68
+ pattern: /from\s+(?:socket|ctypes|pickle|marshal|importlib)\s+import/,
69
+ reason: "restricted module import not allowed"
70
+ },
49
71
  { pattern: /__import__\s*\(/, reason: "__import__ not allowed" },
72
+ { pattern: /\bimportlib\b/, reason: "importlib access not allowed" },
50
73
  { pattern: /os\.system\s*\(/, reason: "os.system not allowed" },
51
74
  { pattern: /os\.popen\s*\(/, reason: "os.popen not allowed" },
52
75
  { pattern: /os\.exec\w*\s*\(/, reason: "os.exec* not allowed" },
53
76
  { pattern: /os\.spawn\w*\s*\(/, reason: "os.spawn* not allowed" },
77
+ { pattern: /os\.fork\s*\(/, reason: "os.fork not allowed" },
78
+ // getattr/setattr can reconstruct blocked names like getattr(os,'sys'+'tem').
79
+ { pattern: /\bgetattr\s*\(/, reason: "getattr not allowed (can bypass name checks)" },
80
+ { pattern: /\bsetattr\s*\(/, reason: "setattr not allowed" },
81
+ { pattern: /\b__builtins__\b/, reason: "__builtins__ access not allowed" },
54
82
  { pattern: /\beval\s*\(/, reason: "eval not allowed" },
55
83
  { pattern: /\bexec\s*\(/, reason: "exec not allowed" },
56
84
  { pattern: /\bcompile\s*\(/, reason: "compile not allowed" }
@@ -121,8 +149,8 @@ const PYTHON_BUILTINS = [
121
149
  const PYTHON_IMPORT_NAME_MAP = {
122
150
  "python-docx": "docx",
123
151
  "python-pptx": "pptx",
124
- "Pillow": "PIL",
125
- "pyyaml": "yaml"
152
+ Pillow: "PIL",
153
+ pyyaml: "yaml"
126
154
  };
127
155
  class CodeValidator {
128
156
  /**
@@ -138,20 +166,29 @@ class CodeValidator {
138
166
  }
139
167
  }
140
168
  /**
141
- * Validate that code only imports packages in the whitelist.
142
- * Called after the basic forbidden pattern check.
143
- * Skips validation if whitelist is empty (env not initialized yet).
169
+ * Validate that code only imports packages in the allowlist (builtins +
170
+ * whitelist). Called after the basic forbidden pattern check.
171
+ *
172
+ * An empty whitelist does NOT skip validation: only built-in modules are
173
+ * then allowed. This closes an exfiltration hole — stdlib modules such as
174
+ * Python `urllib`/`socket`/`ftplib` need no install, so skipping the check
175
+ * when the env was not initialized would let a skill open outbound
176
+ * connections. Set SKILL_HUB_ALLOW_ANY_IMPORT=true to restore the old
177
+ * skip-when-empty behaviour for legacy deployments.
144
178
  *
145
179
  * @param code - The code to validate
146
180
  * @param language - 'node' or 'python'
147
- * @param whitelist - Array of allowed package names (from skillWorkerConfigs.packageWhitelist)
181
+ * @param whitelist - Allowed package names (from skillWorkerConfigs.packageWhitelist)
148
182
  */
149
183
  validateImports(code, language, whitelist) {
150
- if (!(whitelist == null ? void 0 : whitelist.length)) return;
184
+ if (!(whitelist == null ? void 0 : whitelist.length) && process.env.SKILL_HUB_ALLOW_ANY_IMPORT === "true") {
185
+ return;
186
+ }
187
+ const list = whitelist || [];
151
188
  if (language === "node") {
152
- this.validateNodeImports(code, whitelist);
189
+ this.validateNodeImports(code, list);
153
190
  } else if (language === "python") {
154
- this.validatePythonImports(code, whitelist);
191
+ this.validatePythonImports(code, list);
155
192
  }
156
193
  }
157
194
  /**
@@ -178,10 +215,7 @@ class CodeValidator {
178
215
  */
179
216
  validatePythonImports(code, whitelist) {
180
217
  const imports = [...code.matchAll(/(?:^|\n)\s*(?:import|from)\s+([a-zA-Z_][a-zA-Z0-9_]*)/g)];
181
- const allowedImports = /* @__PURE__ */ new Set([
182
- ...PYTHON_BUILTINS,
183
- ...whitelist.map((p) => PYTHON_IMPORT_NAME_MAP[p] || p)
184
- ]);
218
+ const allowedImports = /* @__PURE__ */ new Set([...PYTHON_BUILTINS, ...whitelist.map((p) => PYTHON_IMPORT_NAME_MAP[p] || p)]);
185
219
  for (const match of imports) {
186
220
  const pkg = match[1];
187
221
  if (allowedImports.has(pkg)) continue;
@@ -32,7 +32,12 @@ module.exports = __toCommonJS(SandboxRunner_exports);
32
32
  var import_child_process = require("child_process");
33
33
  var import_fs = require("fs");
34
34
  var import_path = require("path");
35
+ var import_os = require("os");
35
36
  var import_CodeValidator = require("./CodeValidator");
37
+ const IS_WINDOWS = process.platform === "win32";
38
+ function pythonBinary() {
39
+ return process.env.SKILL_HUB_PYTHON_BIN || (IS_WINDOWS ? "python" : "python3");
40
+ }
36
41
  class SandboxRunner {
37
42
  constructor(fileManager, logger, storagePath) {
38
43
  this.fileManager = fileManager;
@@ -55,23 +60,22 @@ class SandboxRunner {
55
60
  skillDir
56
61
  } = options;
57
62
  this.validator.validate(code, language);
58
- if (packageWhitelist == null ? void 0 : packageWhitelist.length) {
59
- this.validator.validateImports(code, language, packageWhitelist);
60
- }
63
+ this.validator.validateImports(code, language, packageWhitelist || []);
61
64
  const workDir = this.fileManager.createExecDir(execId);
62
65
  const outputDir = this.fileManager.getOutputDir(execId);
63
66
  const filename = language === "node" ? "script.js" : "script.py";
64
67
  const scriptPath = (0, import_path.resolve)(workDir, filename);
65
68
  (0, import_fs.writeFileSync)(scriptPath, code, "utf-8");
66
69
  onProgress == null ? void 0 : onProgress({ percent: 20, log: "Code validated, executing..." });
67
- const cmd = language === "node" ? `node "${scriptPath}"` : `python3 "${scriptPath}"`;
68
- const path = require("path");
69
- const nodePath = path.resolve(this.sandboxWorkspace, "node_modules");
70
- const finalNodePath = process.env.NODE_PATH ? `${nodePath}${path.delimiter}${process.env.NODE_PATH}` : nodePath;
70
+ const nodePath = (0, import_path.resolve)(this.sandboxWorkspace, "node_modules");
71
+ const finalNodePath = process.env.NODE_PATH ? `${nodePath}${import_path.delimiter}${process.env.NODE_PATH}` : nodePath;
72
+ const memLimitMb = Math.max(64, Number(process.env.SKILL_HUB_MEM_LIMIT_MB || 512));
73
+ const baseCmd = language === "node" ? `node --max-old-space-size=${memLimitMb} "${scriptPath}"` : `${pythonBinary()} "${scriptPath}"`;
74
+ const cmd = !IS_WINDOWS && memLimitMb > 0 ? `ulimit -v ${memLimitMb * 1024} 2>/dev/null; exec ${baseCmd}` : baseCmd;
71
75
  const startTime = Date.now();
72
76
  let childProc = null;
73
77
  let wasCanceled = false;
74
- const processResult = await new Promise((resolveResult) => {
78
+ const processResult = await new Promise((_resolve) => {
75
79
  childProc = (0, import_child_process.exec)(
76
80
  cmd,
77
81
  {
@@ -82,18 +86,18 @@ class SandboxRunner {
82
86
  env: {
83
87
  // Sanitized environment — only expose what's necessary
84
88
  PATH: process.env.PATH,
85
- HOME: "/tmp",
86
- TMPDIR: "/tmp",
89
+ HOME: (0, import_os.tmpdir)(),
90
+ TMPDIR: (0, import_os.tmpdir)(),
87
91
  OUTPUT_DIR: outputDir,
88
92
  LANG: "en_US.UTF-8",
89
93
  // Node.js
90
94
  NODE_PATH: finalNodePath,
91
95
  // Python — include bundled packages (svg_to_pptx etc.)
92
96
  PYTHONPATH: [
93
- skillDir ? path.resolve(skillDir, "scripts") : "",
94
- path.resolve(this.sandboxWorkspace, "python_packages"),
97
+ skillDir ? (0, import_path.resolve)(skillDir, "scripts") : "",
98
+ (0, import_path.resolve)(this.sandboxWorkspace, "python_packages"),
95
99
  process.env.PYTHONPATH || ""
96
- ].filter(Boolean).join(path.delimiter),
100
+ ].filter(Boolean).join(import_path.delimiter),
97
101
  PYTHONIOENCODING: "utf-8",
98
102
  SKILL_DIR: skillDir || ""
99
103
  // DO NOT pass: DB credentials, API keys, APP_KEY, etc.
@@ -108,7 +112,7 @@ class SandboxRunner {
108
112
  exitCode = error.code || 1;
109
113
  }
110
114
  }
111
- resolveResult({
115
+ _resolve({
112
116
  exitCode,
113
117
  stdout: (stdout || "").slice(0, 5e3),
114
118
  stderr: (stderr || "").slice(0, 2e3)
@@ -54,6 +54,29 @@ var import_SkillRepositoryService = require("../services/SkillRepositoryService"
54
54
  var import_git_import = require("./actions/git-import");
55
55
  var import_json_fields = require("./utils/json-fields");
56
56
  var import_ExecutionSpanService = require("../services/ExecutionSpanService");
57
+ var import_ai_manager = require("../utils/ai-manager");
58
+ function normalizeToolRuntime(runtime) {
59
+ if (!runtime) return void 0;
60
+ if (typeof runtime === "string") {
61
+ return { toolCallId: runtime, writer: () => {
62
+ } };
63
+ }
64
+ return runtime;
65
+ }
66
+ function assignToolRuntime(ctx, runtime) {
67
+ const normalizedRuntime = normalizeToolRuntime(runtime);
68
+ if (!ctx || !normalizedRuntime) return () => {
69
+ };
70
+ const previousRuntime = ctx.runtime;
71
+ ctx.runtime = normalizedRuntime;
72
+ return () => {
73
+ if (previousRuntime === void 0) {
74
+ delete ctx.runtime;
75
+ } else {
76
+ ctx.runtime = previousRuntime;
77
+ }
78
+ };
79
+ }
57
80
  class RateLimiter {
58
81
  constructor(maxExecutions = 10, windowMs = 60 * 1e3) {
59
82
  this.maxExecutions = maxExecutions;
@@ -538,15 +561,14 @@ class SkillHubSubFeature {
538
561
  return configsBySkillId;
539
562
  }
540
563
  registerAITools() {
541
- var _a;
542
564
  try {
543
- const aiPlugin = this.app.pm.get("@nocobase/plugin-ai");
544
- if (!((_a = aiPlugin == null ? void 0 : aiPlugin.ai) == null ? void 0 : _a.toolsManager)) {
565
+ const toolsManager = (0, import_ai_manager.tryGetAIToolsManager)(this.app);
566
+ if (!toolsManager) {
545
567
  this.app.logger.warn("[skill-hub] plugin-ai not available, skip AI tool registration.");
546
568
  return;
547
569
  }
548
- aiPlugin.ai.toolsManager.registerTools((0, import_skill_execute.createSkillExecuteTool)(this));
549
- aiPlugin.ai.toolsManager.registerDynamicTools(async (register) => {
570
+ toolsManager.registerTools((0, import_skill_execute.createSkillExecuteTool)(this));
571
+ toolsManager.registerDynamicTools(async (register) => {
550
572
  try {
551
573
  const skills = await this.db.getRepository("skillDefinitions").find({
552
574
  filter: { enabled: true }
@@ -585,19 +607,24 @@ IMPORTANT: This skill is bound to a Skill Hub human-in-the-loop review template.
585
607
  description,
586
608
  schema: (0, import_json_fields.parseJsonText)(skill.get("inputSchema"), { type: "object", properties: {} })
587
609
  },
588
- invoke: async (toolCtx, args) => {
589
- const latestSkill = await this.db.getRepository("skillDefinitions").findOne({
590
- filter: { id: skill.get("id"), enabled: true }
591
- });
592
- if (!latestSkill) {
593
- return { status: "error", content: `Skill "${skill.get("name")}" is no longer available` };
610
+ invoke: async (toolCtx, args, runtime) => {
611
+ const restoreRuntime = assignToolRuntime(toolCtx, runtime);
612
+ try {
613
+ const latestSkill = await this.db.getRepository("skillDefinitions").findOne({
614
+ filter: { id: skill.get("id"), enabled: true }
615
+ });
616
+ if (!latestSkill) {
617
+ return { status: "error", content: `Skill "${skill.get("name")}" is no longer available` };
618
+ }
619
+ const result = await this.executeSkill(latestSkill, args, toolCtx);
620
+ return {
621
+ status: result.status === "succeeded" ? "success" : "error",
622
+ result
623
+ // Attach raw result
624
+ };
625
+ } finally {
626
+ restoreRuntime();
594
627
  }
595
- const result = await this.executeSkill(latestSkill, args, toolCtx);
596
- return {
597
- status: result.status === "succeeded" ? "success" : "error",
598
- result
599
- // Attach raw result
600
- };
601
628
  }
602
629
  };
603
630
  })
@@ -40,6 +40,9 @@ const ORCHESTRATOR_PATH_KEY = "__orchestratorPath";
40
40
  const MAX_DISPATCH_CONCURRENCY = 5;
41
41
  const MAX_DISPATCH_TASKS = 20;
42
42
  const MAX_TOOL_NAME_LENGTH = 64;
43
+ function getToolCallId(runtime) {
44
+ return typeof runtime === "string" ? runtime : runtime == null ? void 0 : runtime.toolCallId;
45
+ }
43
46
  function sanitizeToolPart(value) {
44
47
  return (value || "").replace(/[^a-zA-Z0-9_-]/g, "_");
45
48
  }
@@ -110,7 +113,8 @@ function createDelegateToolOptions(plugin, options) {
110
113
  context: import_zod.z.string().optional().describe("Optional additional context to help the sub-agent understand the task better.")
111
114
  })
112
115
  },
113
- invoke: async (ctx, args, id) => {
116
+ invoke: async (ctx, args, runtime) => {
117
+ const id = getToolCallId(runtime) || `delegate-${Date.now()}`;
114
118
  const callingEmployee = await resolveCallingEmployee(ctx, plugin);
115
119
  if (!callingEmployee) {
116
120
  await logDelegation(ctx, plugin, {
@@ -224,8 +228,9 @@ ${subAgentList}`
224
228
  ).min(1).max(MAX_DISPATCH_TASKS).describe(`List of sub-tasks to dispatch concurrently. Up to ${MAX_DISPATCH_CONCURRENCY} run in parallel.`)
225
229
  })
226
230
  },
227
- invoke: async (ctx, args, id) => {
231
+ invoke: async (ctx, args, runtime) => {
228
232
  var _a;
233
+ const id = getToolCallId(runtime) || `dispatch-${Date.now()}`;
229
234
  const callingEmployee = await resolveCallingEmployee(ctx, plugin);
230
235
  if (!callingEmployee) {
231
236
  const distinctSubs = Array.from(new Set((args.tasks ?? []).map((t) => t.subAgent).filter(Boolean)));
@@ -30,10 +30,25 @@ __export(skill_execute_exports, {
30
30
  });
31
31
  module.exports = __toCommonJS(skill_execute_exports);
32
32
  var import_json_fields = require("../skill-hub/utils/json-fields");
33
+ function normalizeRuntime(runtime) {
34
+ if (!runtime) return void 0;
35
+ if (typeof runtime === "string") {
36
+ return { toolCallId: runtime, writer: () => {
37
+ } };
38
+ }
39
+ return runtime;
40
+ }
33
41
  function createSkillExecuteTool(plugin) {
34
42
  return {
35
43
  scope: "CUSTOM",
36
44
  execution: "backend",
45
+ // Intentionally fixed to ASK. This is the universal gateway: a single tool
46
+ // whose `execute` action can run ANY enabled skill by name, so the
47
+ // per-skill `autoCall` flag cannot be honored here — the harness decides
48
+ // approval from `defaultPermission` before invoke, when the target skill is
49
+ // not yet known. The per-skill dynamic tools (`skill_hub_<name>`) are the
50
+ // fast path that respect `autoCall: true → ALLOW`. Keeping the generic
51
+ // gateway on ASK is deliberate defense-in-depth, not an oversight.
37
52
  defaultPermission: "ASK",
38
53
  introduction: {
39
54
  title: "Skill Hub - Universal Skill Executor",
@@ -69,7 +84,7 @@ IMPORTANT: If the skill returns file download URLs, you MUST format them as clic
69
84
  required: ["action"]
70
85
  }
71
86
  },
72
- async invoke(ctx, args, _id) {
87
+ async invoke(ctx, args, runtime) {
73
88
  var _a;
74
89
  plugin.app.logger.info(`[skill-execute] Tool invoked with action: ${args.action}, skillName: ${args.skillName}`);
75
90
  if (args.action === "list") {
@@ -134,7 +149,23 @@ IMPORTANT: If the skill returns file download URLs, you MUST format them as clic
134
149
  };
135
150
  }
136
151
  try {
137
- const result = await plugin.executeSkill(skill, args.input || {}, ctx);
152
+ const normalizedRuntime = normalizeRuntime(runtime);
153
+ const previousRuntime = ctx.runtime;
154
+ if (normalizedRuntime) {
155
+ ctx.runtime = normalizedRuntime;
156
+ }
157
+ let result;
158
+ try {
159
+ result = await plugin.executeSkill(skill, args.input || {}, ctx);
160
+ } finally {
161
+ if (normalizedRuntime) {
162
+ if (previousRuntime === void 0) {
163
+ delete ctx.runtime;
164
+ } else {
165
+ ctx.runtime = previousRuntime;
166
+ }
167
+ }
168
+ }
138
169
  return {
139
170
  status: result.status === "succeeded" ? "success" : "error",
140
171
  content: JSON.stringify({
@@ -0,0 +1,51 @@
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 ai_manager_exports = {};
28
+ __export(ai_manager_exports, {
29
+ getAIToolsManager: () => getAIToolsManager,
30
+ tryGetAIToolsManager: () => tryGetAIToolsManager
31
+ });
32
+ module.exports = __toCommonJS(ai_manager_exports);
33
+ function getAIToolsManager(app) {
34
+ var _a;
35
+ const toolsManager = (_a = app == null ? void 0 : app.aiManager) == null ? void 0 : _a.toolsManager;
36
+ if (!toolsManager) {
37
+ throw new Error(
38
+ "[AgentOrchestrator] app.aiManager.toolsManager is not available. Ensure @nocobase/plugin-ai is enabled and loaded before plugin-agent-orchestrator."
39
+ );
40
+ }
41
+ return toolsManager;
42
+ }
43
+ function tryGetAIToolsManager(app) {
44
+ var _a;
45
+ return (_a = app == null ? void 0 : app.aiManager) == null ? void 0 : _a.toolsManager;
46
+ }
47
+ // Annotate the CommonJS export names for ESM import in node:
48
+ 0 && (module.exports = {
49
+ getAIToolsManager,
50
+ tryGetAIToolsManager
51
+ });
@@ -30,6 +30,7 @@ __export(ctx_utils_exports, {
30
30
  asObject: () => asObject,
31
31
  captureCtxSnapshot: () => captureCtxSnapshot,
32
32
  currentUserId: () => currentUserId,
33
+ isAdminUser: () => isAdminUser,
33
34
  normalizeEmployeeUsername: () => normalizeEmployeeUsername,
34
35
  normalizePlanKey: () => normalizePlanKey,
35
36
  normalizeStepType: () => normalizeStepType,
@@ -79,6 +80,15 @@ function currentUserId(ctx) {
79
80
  var _a, _b, _c, _d;
80
81
  return ((_b = (_a = ctx == null ? void 0 : ctx.state) == null ? void 0 : _a.currentUser) == null ? void 0 : _b.id) || ((_d = (_c = ctx == null ? void 0 : ctx.auth) == null ? void 0 : _c.user) == null ? void 0 : _d.id);
81
82
  }
83
+ function isAdminUser(ctx) {
84
+ var _a, _b, _c, _d;
85
+ const roles = ((_b = (_a = ctx == null ? void 0 : ctx.state) == null ? void 0 : _a.currentUser) == null ? void 0 : _b.roles) || ((_d = (_c = ctx == null ? void 0 : ctx.auth) == null ? void 0 : _c.user) == null ? void 0 : _d.roles);
86
+ if (!Array.isArray(roles)) return false;
87
+ return roles.some((r) => {
88
+ const name = typeof r === "string" ? r : r == null ? void 0 : r.name;
89
+ return name === "root" || name === "admin";
90
+ });
91
+ }
82
92
  function valuesFromCtx(ctx) {
83
93
  var _a, _b, _c;
84
94
  return ((_b = (_a = ctx == null ? void 0 : ctx.action) == null ? void 0 : _a.params) == null ? void 0 : _b.values) || ((_c = ctx == null ? void 0 : ctx.request) == null ? void 0 : _c.body) || {};
@@ -139,6 +149,7 @@ function nowIso() {
139
149
  asObject,
140
150
  captureCtxSnapshot,
141
151
  currentUserId,
152
+ isAdminUser,
142
153
  normalizeEmployeeUsername,
143
154
  normalizePlanKey,
144
155
  normalizeStepType,
@@ -0,0 +1,122 @@
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 skill_settings_exports = {};
28
+ __export(skill_settings_exports, {
29
+ isOrchestratorToolName: () => isOrchestratorToolName,
30
+ normalizeAIEmployeeSkillSettings: () => normalizeAIEmployeeSkillSettings
31
+ });
32
+ module.exports = __toCommonJS(skill_settings_exports);
33
+ function isRecord(value) {
34
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
35
+ }
36
+ function isOrchestratorToolName(name) {
37
+ return name === "orchestrator_plan_goal" || name === "orchestrator_execute_plan" || name === "orchestrator_status" || name === "orchestrator_cancel" || name === "external_rag_search" || name === "skill_hub_execute" || name.startsWith("delegate_") || name.startsWith("dispatch_subagents_") || name.startsWith("skill_hub_") || name.startsWith("browser_") || name.startsWith("drawio-");
38
+ }
39
+ function toToolBinding(value) {
40
+ if (typeof value === "string") {
41
+ const name2 = value.trim();
42
+ return name2 ? { name: name2, autoCall: false } : null;
43
+ }
44
+ if (!isRecord(value) || typeof value.name !== "string") {
45
+ return null;
46
+ }
47
+ const name = value.name.trim();
48
+ if (!name) {
49
+ return null;
50
+ }
51
+ return {
52
+ name,
53
+ autoCall: value.autoCall === true
54
+ };
55
+ }
56
+ function addToolBinding(toolsByName, binding) {
57
+ if (!binding) return;
58
+ if (!toolsByName.has(binding.name)) {
59
+ toolsByName.set(binding.name, binding);
60
+ }
61
+ }
62
+ function normalizeAIEmployeeSkillSettings(value) {
63
+ const source = isRecord(value) ? value : {};
64
+ const toolsByName = /* @__PURE__ */ new Map();
65
+ const nextSkills = [];
66
+ let changed = false;
67
+ const sourceTools = source.tools;
68
+ if (Array.isArray(sourceTools)) {
69
+ for (const item of sourceTools) {
70
+ const binding = toToolBinding(item);
71
+ if (binding) {
72
+ addToolBinding(toolsByName, binding);
73
+ if (typeof item === "string") {
74
+ changed = true;
75
+ }
76
+ } else {
77
+ changed = true;
78
+ }
79
+ }
80
+ }
81
+ const sourceSkills = source.skills;
82
+ if (Array.isArray(sourceSkills)) {
83
+ for (const item of sourceSkills) {
84
+ if (typeof item === "string") {
85
+ const name = item.trim();
86
+ if (!name) {
87
+ changed = true;
88
+ continue;
89
+ }
90
+ if (isOrchestratorToolName(name)) {
91
+ addToolBinding(toolsByName, { name, autoCall: false });
92
+ changed = true;
93
+ continue;
94
+ }
95
+ nextSkills.push(name);
96
+ if (name !== item) {
97
+ changed = true;
98
+ }
99
+ continue;
100
+ }
101
+ const legacyToolBinding = toToolBinding(item);
102
+ if (legacyToolBinding) {
103
+ addToolBinding(toolsByName, legacyToolBinding);
104
+ }
105
+ changed = true;
106
+ }
107
+ }
108
+ const normalized = {
109
+ ...source,
110
+ skills: nextSkills,
111
+ tools: Array.from(toolsByName.values())
112
+ };
113
+ return {
114
+ changed,
115
+ skillSettings: normalized
116
+ };
117
+ }
118
+ // Annotate the CommonJS export names for ESM import in node:
119
+ 0 && (module.exports = {
120
+ isOrchestratorToolName,
121
+ normalizeAIEmployeeSkillSettings
122
+ });
package/package.json CHANGED
@@ -1,45 +1,49 @@
1
- {
2
- "name": "plugin-agent-orchestrator",
3
- "displayName": "Agent Orchestrator",
4
- "displayName.zh-CN": "代理协调器",
5
- "displayName.vi-VN": "Điều phối Agent",
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.22",
8
- "license": "Apache-2.0",
9
- "main": "dist/server/index.js",
10
- "keywords": [
11
- "AI",
12
- "Agent",
13
- "Orchestrator",
14
- "Multi-Agent",
15
- "Swarm"
16
- ],
17
- "files": [
18
- "dist",
19
- "src",
20
- "client.js",
21
- "server.js",
22
- "client.d.ts",
23
- "server.d.ts"
24
- ],
25
- "nocobase": {
26
- "supportedVersions": [
27
- "2.x"
28
- ],
29
- "editionLevel": 0
30
- },
31
- "dependencies": {
32
- "@langchain/core": "^0.3.0",
33
- "@langchain/langgraph": "^0.2.0",
34
- "adm-zip": "^0.5.10",
35
- "simple-git": "^3.22.0",
36
- "zod": "^3.23.0"
37
- },
38
- "peerDependencies": {
39
- "@nocobase/client": "2.x",
40
- "@nocobase/server": "2.x",
41
- "@nocobase/database": "2.x",
42
- "@nocobase/ai": "2.x",
43
- "@nocobase/plugin-ai": "2.x"
44
- }
45
- }
1
+ {
2
+ "name": "plugin-agent-orchestrator",
3
+ "displayName": "Agent Orchestrator",
4
+ "displayName.zh-CN": "代理协调器",
5
+ "displayName.vi-VN": "Điều phối Agent",
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.25",
8
+ "license": "Apache-2.0",
9
+ "main": "dist/server/index.js",
10
+ "keywords": [
11
+ "AI",
12
+ "Agent",
13
+ "Orchestrator",
14
+ "Multi-Agent",
15
+ "Swarm"
16
+ ],
17
+ "files": [
18
+ "dist",
19
+ "src",
20
+ "client.js",
21
+ "server.js",
22
+ "client.d.ts",
23
+ "server.d.ts",
24
+ "client-v2.js",
25
+ "client-v2.d.ts"
26
+ ],
27
+ "nocobase": {
28
+ "supportedVersions": [
29
+ "2.x"
30
+ ],
31
+ "editionLevel": 0
32
+ },
33
+ "dependencies": {
34
+ "@langchain/core": "^0.3.0",
35
+ "@langchain/langgraph": "^0.2.0",
36
+ "adm-zip": "^0.5.10",
37
+ "simple-git": "^3.22.0",
38
+ "zod": "^3.23.0"
39
+ },
40
+ "peerDependencies": {
41
+ "@nocobase/client": "2.x",
42
+ "@nocobase/server": "2.x",
43
+ "@nocobase/database": "2.x",
44
+ "@nocobase/ai": "2.x",
45
+ "@nocobase/plugin-ai": "2.x",
46
+ "@nocobase/client-v2": "2.x",
47
+ "@nocobase/flow-engine": "2.x"
48
+ }
49
+ }