plugin-agent-orchestrator 1.0.27 → 1.0.32

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 (110) hide show
  1. package/README.md +9 -7
  2. package/dist/client/index.js +1 -1
  3. package/dist/client-v2/{214.723affb37c13bf7a.js → 214.79650a549273f163.js} +1 -1
  4. package/dist/client-v2/264.718a107e43fc163c.js +10 -0
  5. package/dist/client-v2/373.f5d5292e53c4e832.js +10 -0
  6. package/dist/client-v2/{41.1805b2edfaa4afe2.js → 41.ba6e080cc0488143.js} +1 -1
  7. package/dist/client-v2/418.29e713f79131eece.js +10 -0
  8. package/dist/client-v2/619.bd3c5698b40705c3.js +10 -0
  9. package/dist/client-v2/677.a991ce0250ff5c77.js +10 -0
  10. package/dist/client-v2/{70.a15d7fcec7c41768.js → 70.bda9518881c05360.js} +1 -1
  11. package/dist/client-v2/925.f5370de8f6632d65.js +10 -0
  12. package/dist/client-v2/index.js +1 -1
  13. package/dist/externalVersion.js +7 -10
  14. package/dist/locale/en-US.json +94 -25
  15. package/dist/locale/vi-VN.json +94 -25
  16. package/dist/locale/zh-CN.json +94 -25
  17. package/dist/server/collections/agent-execution-spans.js +37 -0
  18. package/dist/server/collections/agent-harness-profiles.js +2 -2
  19. package/dist/server/collections/agent-memory-contexts.js +125 -0
  20. package/dist/server/collections/orchestrator-logs.js +2 -2
  21. package/dist/server/migrations/20260425000000-add-interaction-schema.js +3 -1
  22. package/dist/server/migrations/20260427000000-change-packages-to-text.js +3 -1
  23. package/dist/server/migrations/20260427000001-change-other-json-to-text.js +6 -2
  24. package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.js +21 -19
  25. package/dist/server/migrations/20260621000000-native-policy-profile-defaults.js +193 -0
  26. package/dist/server/plugin.js +128 -74
  27. package/dist/server/resources/agent-monitor.js +454 -0
  28. package/dist/server/services/AgentHarness.js +24 -499
  29. package/dist/server/services/AgentMemoryContextService.js +216 -0
  30. package/dist/server/services/ExecutionSpanService.js +2 -2
  31. package/dist/server/services/NativeSubAgentObserver.js +413 -0
  32. package/dist/server/skill-hub/mcp/McpController.js +16 -5
  33. package/dist/server/skill-hub/plugin.js +81 -5
  34. package/dist/server/skill-hub/tasks/SkillExecutionTask.js +9 -3
  35. package/dist/server/tools/delegate-task.js +11 -589
  36. package/dist/server/utils/skill-settings.js +18 -1
  37. package/package.json +47 -49
  38. package/src/client/AIEmployeesContext.tsx +5 -18
  39. package/src/client/AgentRunsTab.tsx +2 -771
  40. package/src/client/HarnessProfilesTab.tsx +2 -257
  41. package/src/client/OrchestratorSettings.tsx +97 -106
  42. package/src/client/RulesTab.tsx +2 -788
  43. package/src/client/plugin.tsx +0 -2
  44. package/src/client/skill-hub/components/ExecutionHistory.tsx +200 -202
  45. package/src/client/skill-hub/components/ExecutionProgress.tsx +51 -55
  46. package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
  47. package/src/client/skill-hub/components/SkillEditor.tsx +43 -39
  48. package/src/client/skill-hub/components/SkillManager.tsx +194 -181
  49. package/src/client/skill-hub/components/SkillTestPanel.tsx +141 -145
  50. package/src/client/skill-hub/locale.ts +16 -16
  51. package/src/client/skill-hub/tools/SkillHubCard.tsx +104 -109
  52. package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
  53. package/src/client/skill-hub/utils/jsonFields.ts +7 -3
  54. package/src/client-v2/components/AIEmployeesContext.tsx +3 -16
  55. package/src/client-v2/components/AgentRunsTab.tsx +182 -455
  56. package/src/client-v2/components/HarnessProfilesTab.tsx +34 -31
  57. package/src/client-v2/components/RulesTab.tsx +2 -782
  58. package/src/client-v2/components/TracingTab.tsx +1 -1
  59. package/src/client-v2/hooks/useApiRequest.ts +8 -1
  60. package/src/client-v2/pages/RulesPage.tsx +2 -2
  61. package/src/client-v2/plugin.tsx +3 -3
  62. package/src/locale/en-US.json +94 -25
  63. package/src/locale/vi-VN.json +94 -25
  64. package/src/locale/zh-CN.json +94 -25
  65. package/src/server/__tests__/native-sub-agent-observer.test.ts +246 -0
  66. package/src/server/__tests__/skill-settings.test.ts +6 -6
  67. package/src/server/__tests__/smoke.test.ts +1 -0
  68. package/src/server/collections/agent-execution-spans.ts +37 -0
  69. package/src/server/collections/agent-harness-profiles.ts +59 -59
  70. package/src/server/collections/agent-loop-events.ts +71 -71
  71. package/src/server/collections/agent-loop-steps.ts +144 -144
  72. package/src/server/collections/agent-memory-contexts.ts +95 -0
  73. package/src/server/collections/orchestrator-logs.ts +4 -4
  74. package/src/server/collections/skill-definitions.ts +111 -111
  75. package/src/server/collections/skill-executions.ts +106 -106
  76. package/src/server/collections/skill-loop-configs.ts +65 -65
  77. package/src/server/migrations/20260423000000-add-progress-fields.ts +14 -14
  78. package/src/server/migrations/20260425000000-add-interaction-schema.ts +3 -1
  79. package/src/server/migrations/20260427000000-change-packages-to-text.ts +4 -2
  80. package/src/server/migrations/20260427000001-change-other-json-to-text.ts +9 -5
  81. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
  82. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +145 -142
  83. package/src/server/migrations/20260615000000-normalize-ai-employee-tool-bindings.ts +2 -2
  84. package/src/server/migrations/20260621000000-native-policy-profile-defaults.ts +193 -0
  85. package/src/server/plugin.ts +151 -94
  86. package/src/server/resources/agent-monitor.ts +482 -0
  87. package/src/server/services/AgentHarness.ts +38 -623
  88. package/src/server/services/AgentMemoryContextService.ts +256 -0
  89. package/src/server/services/AgentPlanValidator.ts +73 -73
  90. package/src/server/services/ExecutionSpanService.ts +6 -2
  91. package/src/server/services/FileManager.ts +144 -144
  92. package/src/server/services/NativeSubAgentObserver.ts +507 -0
  93. package/src/server/services/SkillManager.ts +583 -583
  94. package/src/server/services/SkillRepositoryService.ts +5 -7
  95. package/src/server/services/TokenTracker.ts +3 -3
  96. package/src/server/services/WorkerEnvManager.ts +1 -2
  97. package/src/server/skill-hub/actions/git-import.ts +5 -7
  98. package/src/server/skill-hub/mcp/McpController.ts +41 -14
  99. package/src/server/skill-hub/plugin.ts +89 -6
  100. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +470 -460
  101. package/src/server/skill-hub/utils/json-fields.ts +1 -1
  102. package/src/server/tools/delegate-task.ts +13 -847
  103. package/src/server/utils/skill-settings.ts +24 -6
  104. package/dist/client-v2/264.0533912e6c5ea2d7.js +0 -10
  105. package/dist/client-v2/418.5ae055abf141820e.js +0 -10
  106. package/dist/client-v2/619.d99d3c9e61c99064.js +0 -10
  107. package/dist/client-v2/892.72db4161511c8a16.js +0 -10
  108. package/dist/client-v2/926.87f660b670d85bcc.js +0 -10
  109. package/src/client/tools/PlanApprovalCard.tsx +0 -176
  110. package/src/client/tools/registerOrchestratorCards.ts +0 -17
@@ -25,7 +25,7 @@ export class SkillRepositoryService {
25
25
  */
26
26
  async extractSkillPackage(skillName: string, zipFilePath: string) {
27
27
  const targetDir = resolve(this.baseDir, skillName);
28
-
28
+
29
29
  // Clean target dir if exists
30
30
  if (existsSync(targetDir)) {
31
31
  rmSync(targetDir, { recursive: true, force: true });
@@ -100,8 +100,6 @@ export class SkillRepositoryService {
100
100
  });
101
101
  }
102
102
 
103
-
104
-
105
103
  private getSkillCodeFromDir(dir: string): string | null {
106
104
  if (existsSync(resolve(dir, 'index.py'))) {
107
105
  return readFileSync(resolve(dir, 'index.py'), 'utf8');
@@ -118,14 +116,14 @@ export class SkillRepositoryService {
118
116
 
119
117
  private aggregateOtherMarkdownFiles(dir: string, baseDir = dir): string {
120
118
  let combined = '';
121
-
119
+
122
120
  if (!existsSync(dir)) return combined;
123
-
121
+
124
122
  const items = readdirSync(dir);
125
123
  for (const item of items) {
126
124
  const fullPath = resolve(dir, item);
127
125
  const stat = statSync(fullPath);
128
-
126
+
129
127
  if (stat.isDirectory()) {
130
128
  if (item !== 'node_modules' && item !== '.git') {
131
129
  combined += this.aggregateOtherMarkdownFiles(fullPath, baseDir);
@@ -136,7 +134,7 @@ export class SkillRepositoryService {
136
134
  combined += `\n\n--- Content from ${relPath} ---\n\n${content}`;
137
135
  }
138
136
  }
139
-
137
+
140
138
  return combined;
141
139
  }
142
140
  }
@@ -32,14 +32,14 @@ function estimateCost(inputTokens: number, outputTokens: number): number {
32
32
  }
33
33
 
34
34
  /**
35
- * Extract usage_metadata from the final state returned by createReactAgent.invoke().
35
+ * Extract usage_metadata from a legacy agent final state.
36
36
  *
37
- * LangChain's createReactAgent returns a final state object with a `messages` array.
37
+ * Legacy agent execution returned a final state object with a `messages` array.
38
38
  * The last AIMessage in that array carries `usage_metadata` populated by the
39
39
  * LLM provider after the final generation step. This is the standard LangChain
40
40
  * approach — no private API access needed.
41
41
  *
42
- * Expected shape (from @langchain/core/messages/ai):
42
+ * Expected message shape:
43
43
  * AIMessage.usage_metadata = {
44
44
  * input_tokens: number,
45
45
  * output_tokens: number,
@@ -101,8 +101,7 @@ export class WorkerEnvManager {
101
101
 
102
102
  await (this as any).app.pubSubManager.publish('skill-hub.init-env.done', {
103
103
  status: 'succeeded',
104
- log:
105
- 'Sandbox environment is ready on this worker. Package installation is managed by the worker image/runtime; whitelist was refreshed.',
104
+ log: 'Sandbox environment is ready on this worker. Package installation is managed by the worker image/runtime; whitelist was refreshed.',
106
105
  whitelist,
107
106
  });
108
107
  }
@@ -162,7 +162,7 @@ export async function gitSyncSkills(ctx: Context, next: () => Promise<void>) {
162
162
  const parsed = parseSkillMarkdown(skillMd);
163
163
  frontmatter = parsed.metadata;
164
164
  instructions = parsed.body;
165
-
165
+
166
166
  const otherMds = await fetchAllMarkdownInFolder(git, ref, skillBaseDir);
167
167
  if (otherMds) instructions += otherMds;
168
168
  } catch {
@@ -422,8 +422,6 @@ function parseBoolean(value: any, fallback: boolean) {
422
422
  return Boolean(value);
423
423
  }
424
424
 
425
-
426
-
427
425
  function extractStringProperty(source: string, property: string) {
428
426
  const match = source.match(new RegExp(`${property}\\s*:\\s*(['"\`])([\\s\\S]*?)\\1`));
429
427
  return match ? match[2] : null;
@@ -470,7 +468,7 @@ async function fetchAllMarkdownInFolder(git: any, ref: string, folderPath: strin
470
468
  try {
471
469
  const filesOut = await git.raw(['ls-tree', '-r', '--name-only', `${ref}:${folderPath}/`]);
472
470
  const files = filesOut.trim().split('\n').filter(Boolean);
473
-
471
+
474
472
  for (const file of files) {
475
473
  if (file.toLowerCase().endsWith('.md') && file.toUpperCase() !== 'SKILL.md') {
476
474
  const content = await readOptionalGitFile(git, ref, joinGitPath(folderPath, file));
@@ -479,8 +477,8 @@ async function fetchAllMarkdownInFolder(git: any, ref: string, folderPath: strin
479
477
  }
480
478
  }
481
479
  }
482
- } catch (err) {}
480
+ } catch (err) {
481
+ // Optional folder expansion can fail for missing paths or refs; keep import best-effort.
482
+ }
483
483
  return combined;
484
484
  }
485
-
486
-
@@ -13,13 +13,20 @@ export class McpController {
13
13
  filter: { enabled: true },
14
14
  });
15
15
 
16
- const tools = await Promise.all(skills.map(async (skill: any) => ({
17
- name: skill.get('name').toLowerCase().replace(/[^a-z0-9_]/g, '_').replace(/_+/g, '_'),
18
- description: typeof this.plugin.getSkillDescriptionForAI === 'function'
19
- ? await this.plugin.getSkillDescriptionForAI(skill)
20
- : skill.get('description'),
16
+ const tools = await Promise.all(
17
+ skills.map(async (skill: any) => ({
18
+ name: skill
19
+ .get('name')
20
+ .toLowerCase()
21
+ .replace(/[^a-z0-9_]/g, '_')
22
+ .replace(/_+/g, '_'),
23
+ description:
24
+ typeof this.plugin.getSkillDescriptionForAI === 'function'
25
+ ? await this.plugin.getSkillDescriptionForAI(skill)
26
+ : skill.get('description'),
21
27
  inputSchema: parseJsonText(skill.get('inputSchema'), null),
22
- })));
28
+ })),
29
+ );
23
30
 
24
31
  ctx.body = {
25
32
  tools,
@@ -44,8 +51,13 @@ export class McpController {
44
51
  filter: { enabled: true },
45
52
  });
46
53
 
47
- const skill = skills.find((s: any) =>
48
- s.get('name').toLowerCase().replace(/[^a-z0-9_]/g, '_').replace(/_+/g, '_') === name
54
+ const skill = skills.find(
55
+ (s: any) =>
56
+ s
57
+ .get('name')
58
+ .toLowerCase()
59
+ .replace(/[^a-z0-9_]/g, '_')
60
+ .replace(/_+/g, '_') === name,
49
61
  );
50
62
 
51
63
  if (!skill) {
@@ -54,15 +66,30 @@ export class McpController {
54
66
 
55
67
  try {
56
68
  const result = await this.plugin.executeSkill(skill, args || {}, ctx);
57
-
69
+
58
70
  let textContent = `Executed successfully.`;
59
71
  if (result.stdout) textContent += `\nOutput:\n${result.stdout}`;
60
72
  if (result.stderr) textContent += `\nErrors:\n${result.stderr}`;
61
-
73
+
62
74
  if (result.files?.length) {
63
- textContent += `\nFiles generated:\n` + result.files.map((f: any) => {
64
- return `- [${f.name}](${f.downloadUrl})`;
65
- }).join('\n');
75
+ textContent +=
76
+ `\nFiles generated:\n` +
77
+ result.files
78
+ .map((f: any) => {
79
+ return `- [${f.name}](${f.downloadUrl})`;
80
+ })
81
+ .join('\n');
82
+
83
+ // Embed a trustworthy file manifest so the chat file-preview plugin can resolve the
84
+ // real download URL by filename, instead of guessing it from LLM-rewritten links.
85
+ const manifest = result.files.map((f: any) => ({
86
+ name: f.name,
87
+ downloadUrl: f.downloadUrl,
88
+ mimetype: f.mimetype ?? f.mimeType ?? null,
89
+ size: f.size ?? null,
90
+ execId: result.execId ?? null,
91
+ }));
92
+ textContent += `\n<!--skillhub:files ${JSON.stringify(manifest)}-->`;
66
93
  }
67
94
 
68
95
  ctx.body = {
@@ -70,7 +97,7 @@ export class McpController {
70
97
  {
71
98
  type: 'text',
72
99
  text: textContent,
73
- }
100
+ },
74
101
  ],
75
102
  isError: result.status !== 'succeeded',
76
103
  };
@@ -16,6 +16,8 @@ import { tryGetAIToolsManager } from '../utils/ai-manager';
16
16
  import type { ToolsRuntime } from '@nocobase/ai';
17
17
 
18
18
  type ToolRuntimeInput = string | ToolsRuntime | undefined;
19
+ const SKILL_TASK_POLL_INTERVAL_MS = Number.parseInt(process.env.SKILL_HUB_TASK_POLL_INTERVAL_MS || '5000', 10);
20
+ const SKILL_TASK_POLL_LIMIT = Number.parseInt(process.env.SKILL_HUB_TASK_POLL_LIMIT || '3', 10);
19
21
 
20
22
  function normalizeToolRuntime(runtime: ToolRuntimeInput): ToolsRuntime | undefined {
21
23
  if (!runtime) return undefined;
@@ -100,6 +102,10 @@ export class SkillHubSubFeature {
100
102
  private cleanupInterval: ReturnType<typeof setInterval> | null = null;
101
103
  private initEnvDoneCallback: any = null;
102
104
  private initEnvProgressCallback: any = null;
105
+ private skillTaskCallback: any = null;
106
+ private initEnvTaskCallback: any = null;
107
+ private skillTaskPoller: ReturnType<typeof setInterval> | null = null;
108
+ private skillTaskPolling = false;
103
109
  private mcpController: McpController;
104
110
  private skillRepoService: SkillRepositoryService;
105
111
  private rateLimiter = new RateLimiter(
@@ -255,21 +261,24 @@ export class SkillHubSubFeature {
255
261
  });
256
262
 
257
263
  // 5. Subscribe PubSub — worker processes skill execution tasks
258
- (this as any).app.pubSubManager.subscribe('skill-hub.task', async (payload: any) => {
264
+ this.skillTaskCallback = async (payload: any) => {
259
265
  if (process.env.SKILL_HUB_SANDBOX === 'false') return;
260
266
  await this.onQueueTask(payload);
261
- });
267
+ };
268
+ (this as any).app.pubSubManager.subscribe('skill-hub.task', this.skillTaskCallback);
262
269
 
263
270
  // 5b. Subscribe PubSub — worker processes init-env tasks
264
- (this as any).app.pubSubManager.subscribe('skill-hub.init-env', async (payload: any) => {
271
+ this.initEnvTaskCallback = async (payload: any) => {
265
272
  if (process.env.SKILL_HUB_SANDBOX === 'false') return;
266
273
  await this.workerEnvManager.executeInit(payload);
267
- });
274
+ };
275
+ (this as any).app.pubSubManager.subscribe('skill-hub.init-env', this.initEnvTaskCallback);
268
276
 
269
277
  // 6. Register AI tools + subscriptions (deferred — after all plugins loaded)
270
278
  (this as any).app.on('afterStart', async () => {
271
279
  this.registerAITools();
272
280
  this.startCleanupInterval();
281
+ this.startSkillTaskPoller();
273
282
  await this.subscribeInitEnvDone();
274
283
  // Ensure any newly added built-in skills are seeded automatically on upgrade/restart
275
284
  await this.skillManager.seedDefaults().catch((e) => {
@@ -280,6 +289,15 @@ export class SkillHubSubFeature {
280
289
 
281
290
  private async onQueueTask(message: { id: string }) {
282
291
  (this as any).app.logger.info(`[skill-hub] Worker received queue task: ${message.id}`);
292
+ if (process.env.SKILL_HUB_SANDBOX === 'false') {
293
+ return;
294
+ }
295
+ const claimed = await this.claimSkillExecution(message.id);
296
+ if (!claimed) {
297
+ (this as any).app.logger.debug(`[skill-hub] Task ${message.id} ignored: already claimed or not pending.`);
298
+ return;
299
+ }
300
+
283
301
  const execution = await (this as any).db.getRepository('skillExecutions').findOne({
284
302
  filter: { id: message.id },
285
303
  appends: ['skill'],
@@ -299,9 +317,56 @@ export class SkillHubSubFeature {
299
317
  await task.run();
300
318
  }
301
319
 
320
+ private async claimSkillExecution(id: string): Promise<boolean> {
321
+ const model = (this as any).db.getModel('skillExecutions');
322
+ const [affected] = await model.update(
323
+ { status: 'running' },
324
+ {
325
+ where: {
326
+ id,
327
+ status: 'pending',
328
+ },
329
+ },
330
+ );
331
+ return affected > 0;
332
+ }
333
+
334
+ private startSkillTaskPoller() {
335
+ if (process.env.SKILL_HUB_SANDBOX === 'false' || this.skillTaskPoller) {
336
+ return;
337
+ }
338
+
339
+ const tick = () => {
340
+ this.processPendingSkillExecutions().catch((error) => {
341
+ (this as any).app.logger.warn(`[skill-hub] Pending task poll failed: ${error?.message || error}`);
342
+ });
343
+ };
344
+
345
+ this.skillTaskPoller = setInterval(tick, SKILL_TASK_POLL_INTERVAL_MS);
346
+ (this.skillTaskPoller as any).unref?.();
347
+ setTimeout(tick, 1000);
348
+ }
349
+
350
+ private async processPendingSkillExecutions() {
351
+ if (this.skillTaskPolling) return;
352
+ this.skillTaskPolling = true;
353
+ try {
354
+ const records = await (this as any).db.getRepository('skillExecutions').find({
355
+ filter: { status: 'pending' },
356
+ sort: ['createdAt'],
357
+ limit: SKILL_TASK_POLL_LIMIT,
358
+ });
359
+ for (const record of records) {
360
+ await this.onQueueTask({ id: String(record.get('id')) });
361
+ }
362
+ } finally {
363
+ this.skillTaskPolling = false;
364
+ }
365
+ }
366
+
302
367
  /**
303
368
  * Execute skill — called by both AI tool and REST test endpoint.
304
- * Dispatches to worker via EventQueue, waits for result via PubSub.
369
+ * Dispatches to sandbox workers via PubSub, waits for result via PubSub.
305
370
  * Pushes progress to SSE via runtime.writer (if within AI tool context).
306
371
  * Includes rate limiting and graceful abort propagation.
307
372
  */
@@ -408,7 +473,7 @@ export class SkillHubSubFeature {
408
473
  });
409
474
  }
410
475
 
411
- // NOW Dispatch to worker via EventQueue
476
+ // NOW dispatch to sandbox workers.
412
477
  await (this as any).app.pubSubManager.publish('skill-hub.task', { id: execId });
413
478
 
414
479
  // Wait for completion
@@ -759,6 +824,20 @@ export class SkillHubSubFeature {
759
824
 
760
825
  async beforeStop() {
761
826
  // Unsubscribe PubSub
827
+ if (this.skillTaskCallback) {
828
+ try {
829
+ await (this as any).app.pubSubManager.unsubscribe('skill-hub.task', this.skillTaskCallback);
830
+ } catch {
831
+ /* ignore */
832
+ }
833
+ }
834
+ if (this.initEnvTaskCallback) {
835
+ try {
836
+ await (this as any).app.pubSubManager.unsubscribe('skill-hub.init-env', this.initEnvTaskCallback);
837
+ } catch {
838
+ /* ignore */
839
+ }
840
+ }
762
841
  if (this.initEnvDoneCallback) {
763
842
  try {
764
843
  await (this as any).app.pubSubManager.unsubscribe('skill-hub.init-env.done', this.initEnvDoneCallback);
@@ -779,6 +858,10 @@ export class SkillHubSubFeature {
779
858
  clearInterval(this.cleanupInterval);
780
859
  this.cleanupInterval = null;
781
860
  }
861
+ if (this.skillTaskPoller) {
862
+ clearInterval(this.skillTaskPoller);
863
+ this.skillTaskPoller = null;
864
+ }
782
865
  }
783
866
 
784
867
  // --- Handlers ---