plugin-agent-orchestrator 1.0.20 → 1.0.22

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 (158) hide show
  1. package/dist/client/index.js +1 -1
  2. package/dist/externalVersion.js +6 -6
  3. package/dist/server/collections/agent-execution-spans.js +24 -0
  4. package/dist/server/collections/agent-loop-runs.js +36 -0
  5. package/dist/server/collections/orchestrator-config.js +14 -0
  6. package/dist/server/migrations/20260601000000-add-token-fields.js +101 -0
  7. package/dist/server/plugin.js +56 -0
  8. package/dist/server/resources/agent-loop.js +33 -25
  9. package/dist/server/resources/tracing.js +5 -8
  10. package/dist/server/services/AgentHarness.js +56 -90
  11. package/dist/server/services/AgentLoopController.js +164 -125
  12. package/dist/server/services/AgentLoopRepository.js +16 -34
  13. package/dist/server/services/AgentLoopService.js +7 -1
  14. package/dist/server/services/AgentPlannerService.js +5 -25
  15. package/dist/server/services/AgentRegistryService.js +34 -24
  16. package/dist/server/services/CircuitBreaker.js +120 -0
  17. package/dist/server/services/ContextAggregator.js +201 -0
  18. package/dist/server/services/ExecutionSpanService.js +2 -5
  19. package/dist/server/services/RunEventBus.js +73 -0
  20. package/dist/server/services/TokenTracker.js +173 -0
  21. package/dist/server/tools/agent-loop.js +30 -63
  22. package/dist/server/tools/delegate-task.js +14 -72
  23. package/dist/server/tools/orchestrator-plan.js +10 -47
  24. package/dist/server/types.js +24 -0
  25. package/dist/server/utils/ctx-utils.js +152 -0
  26. package/dist/server/utils/logging.js +86 -0
  27. package/package.json +44 -44
  28. package/src/client/AgentRunsTab.tsx +764 -764
  29. package/src/client/HarnessProfilesTab.tsx +247 -247
  30. package/src/client/OrchestratorSettings.tsx +106 -106
  31. package/src/client/RulesTab.tsx +716 -716
  32. package/src/client/hooks/useRunEventStream.ts +76 -0
  33. package/src/client/index.tsx +2 -1
  34. package/src/client/plugin.tsx +27 -27
  35. package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
  36. package/src/client/skill-hub/index.tsx +51 -51
  37. package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +99 -99
  38. package/src/client/skill-hub/tools/SkillHubCard.tsx +109 -109
  39. package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
  40. package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -58
  41. package/src/client/tools/PlanApprovalCard.tsx +175 -175
  42. package/src/client/tools/registerOrchestratorCards.ts +7 -7
  43. package/src/server/__tests__/agent-loop-controller.test.ts +375 -0
  44. package/src/server/__tests__/circuit-breaker.test.ts +169 -0
  45. package/src/server/__tests__/context-aggregator.test.ts +222 -0
  46. package/src/server/__tests__/parallel-execution.test.ts +318 -0
  47. package/src/server/__tests__/smoke.test.ts +120 -0
  48. package/src/server/collections/agent-execution-spans.ts +24 -0
  49. package/src/server/collections/agent-harness-profiles.ts +59 -59
  50. package/src/server/collections/agent-loop-events.ts +71 -71
  51. package/src/server/collections/agent-loop-runs.ts +38 -1
  52. package/src/server/collections/agent-loop-steps.ts +144 -144
  53. package/src/server/collections/orchestrator-config.ts +14 -0
  54. package/src/server/collections/skill-executions.ts +106 -106
  55. package/src/server/collections/skill-loop-configs.ts +65 -65
  56. package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
  57. package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -142
  58. package/src/server/migrations/20260601000000-add-token-fields.ts +89 -0
  59. package/src/server/plugin.ts +68 -0
  60. package/src/server/resources/agent-loop.ts +21 -12
  61. package/src/server/resources/tracing.ts +3 -7
  62. package/src/server/services/AgentHarness.ts +78 -116
  63. package/src/server/services/AgentLoopController.ts +197 -122
  64. package/src/server/services/AgentLoopRepository.ts +9 -25
  65. package/src/server/services/AgentLoopService.ts +13 -1
  66. package/src/server/services/AgentPlanValidator.ts +73 -73
  67. package/src/server/services/AgentPlannerService.ts +2 -25
  68. package/src/server/services/AgentRegistryService.ts +40 -31
  69. package/src/server/services/CircuitBreaker.ts +116 -0
  70. package/src/server/services/ContextAggregator.ts +239 -0
  71. package/src/server/services/ExecutionSpanService.ts +2 -4
  72. package/src/server/services/RunEventBus.ts +45 -0
  73. package/src/server/services/TokenTracker.ts +209 -0
  74. package/src/server/skill-hub/plugin.ts +898 -898
  75. package/src/server/skill-hub/tasks/SkillExecutionTask.ts +460 -460
  76. package/src/server/tools/agent-loop.ts +18 -57
  77. package/src/server/tools/delegate-task.ts +11 -93
  78. package/src/server/tools/orchestrator-plan.ts +26 -50
  79. package/src/server/tools/skill-execute.ts +160 -160
  80. package/src/server/types.ts +55 -0
  81. package/src/server/utils/ctx-utils.ts +118 -0
  82. package/src/server/utils/logging.ts +63 -0
  83. package/dist/client/AIEmployeeSelect.d.ts +0 -11
  84. package/dist/client/AIEmployeesContext.d.ts +0 -30
  85. package/dist/client/AgentRunsTab.d.ts +0 -2
  86. package/dist/client/HarnessProfilesTab.d.ts +0 -2
  87. package/dist/client/OrchestratorSettings.d.ts +0 -3
  88. package/dist/client/RulesTab.d.ts +0 -2
  89. package/dist/client/TracingTab.d.ts +0 -2
  90. package/dist/client/index.d.ts +0 -1
  91. package/dist/client/plugin.d.ts +0 -6
  92. package/dist/client/skill-hub/components/ExecutionHistory.d.ts +0 -2
  93. package/dist/client/skill-hub/components/ExecutionProgress.d.ts +0 -20
  94. package/dist/client/skill-hub/components/GitSkillImport.d.ts +0 -7
  95. package/dist/client/skill-hub/components/LoopSettings.d.ts +0 -2
  96. package/dist/client/skill-hub/components/SkillEditor.d.ts +0 -7
  97. package/dist/client/skill-hub/components/SkillManager.d.ts +0 -2
  98. package/dist/client/skill-hub/components/SkillMetrics.d.ts +0 -2
  99. package/dist/client/skill-hub/components/SkillTestPanel.d.ts +0 -7
  100. package/dist/client/skill-hub/index.d.ts +0 -11
  101. package/dist/client/skill-hub/locale.d.ts +0 -3
  102. package/dist/client/skill-hub/tools/InteractionSchemasProvider.d.ts +0 -6
  103. package/dist/client/skill-hub/tools/SkillHubCard.d.ts +0 -3
  104. package/dist/client/skill-hub/tools/loopTemplates.d.ts +0 -22
  105. package/dist/client/skill-hub/tools/registerSkillLoopCards.d.ts +0 -1
  106. package/dist/client/skill-hub/utils/jsonFields.d.ts +0 -3
  107. package/dist/client/tools/PlanApprovalCard.d.ts +0 -3
  108. package/dist/client/tools/registerOrchestratorCards.d.ts +0 -1
  109. package/dist/index.d.ts +0 -2
  110. package/dist/server/collections/agent-execution-spans.d.ts +0 -9
  111. package/dist/server/collections/agent-harness-profiles.d.ts +0 -2
  112. package/dist/server/collections/agent-loop-events.d.ts +0 -2
  113. package/dist/server/collections/agent-loop-runs.d.ts +0 -2
  114. package/dist/server/collections/agent-loop-steps.d.ts +0 -2
  115. package/dist/server/collections/orchestrator-config.d.ts +0 -2
  116. package/dist/server/collections/orchestrator-logs.d.ts +0 -8
  117. package/dist/server/collections/skill-definitions.d.ts +0 -3
  118. package/dist/server/collections/skill-executions.d.ts +0 -3
  119. package/dist/server/collections/skill-loop-configs.d.ts +0 -3
  120. package/dist/server/collections/skill-worker-configs.d.ts +0 -3
  121. package/dist/server/index.d.ts +0 -1
  122. package/dist/server/migrations/20260423000000-add-progress-fields.d.ts +0 -4
  123. package/dist/server/migrations/20260425000000-add-interaction-schema.d.ts +0 -4
  124. package/dist/server/migrations/20260427000000-add-tracing-detail-fields.d.ts +0 -7
  125. package/dist/server/migrations/20260427000000-change-packages-to-text.d.ts +0 -4
  126. package/dist/server/migrations/20260427000001-change-other-json-to-text.d.ts +0 -4
  127. package/dist/server/migrations/20260429000000-add-llm-fields.d.ts +0 -7
  128. package/dist/server/migrations/20260429000000-fix-inputargs-json-to-text.d.ts +0 -16
  129. package/dist/server/migrations/20260503000000-add-orchestrator-trace-fields.d.ts +0 -7
  130. package/dist/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.d.ts +0 -7
  131. package/dist/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.d.ts +0 -12
  132. package/dist/server/plugin.d.ts +0 -16
  133. package/dist/server/resources/agent-loop.d.ts +0 -3
  134. package/dist/server/resources/tracing.d.ts +0 -7
  135. package/dist/server/services/AgentHarness.d.ts +0 -42
  136. package/dist/server/services/AgentLoopController.d.ts +0 -205
  137. package/dist/server/services/AgentLoopRepository.d.ts +0 -20
  138. package/dist/server/services/AgentLoopService.d.ts +0 -149
  139. package/dist/server/services/AgentPlanValidator.d.ts +0 -4
  140. package/dist/server/services/AgentPlannerService.d.ts +0 -8
  141. package/dist/server/services/AgentRegistryService.d.ts +0 -13
  142. package/dist/server/services/CodeValidator.d.ts +0 -32
  143. package/dist/server/services/ExecutionSpanService.d.ts +0 -46
  144. package/dist/server/services/FileManager.d.ts +0 -28
  145. package/dist/server/services/SandboxRunner.d.ts +0 -41
  146. package/dist/server/services/SkillManager.d.ts +0 -6
  147. package/dist/server/services/SkillRepositoryService.d.ts +0 -22
  148. package/dist/server/services/WorkerEnvManager.d.ts +0 -26
  149. package/dist/server/skill-hub/actions/git-import.d.ts +0 -21
  150. package/dist/server/skill-hub/mcp/McpController.d.ts +0 -15
  151. package/dist/server/skill-hub/plugin.d.ts +0 -61
  152. package/dist/server/skill-hub/tasks/SkillExecutionTask.d.ts +0 -16
  153. package/dist/server/skill-hub/utils/json-fields.d.ts +0 -7
  154. package/dist/server/tools/agent-loop.d.ts +0 -235
  155. package/dist/server/tools/delegate-task.d.ts +0 -19
  156. package/dist/server/tools/external-rag-search.d.ts +0 -42
  157. package/dist/server/tools/orchestrator-plan.d.ts +0 -205
  158. package/dist/server/tools/skill-execute.d.ts +0 -36
@@ -1,142 +1,142 @@
1
- import { DataTypes } from '@nocobase/database';
2
- import { Migration } from '@nocobase/server';
3
-
4
- export default class AddPlanApprovalAndHarnessProfiles extends Migration {
5
- on = 'afterLoad';
6
- appVersion = '>=0.1.0';
7
-
8
- async up() {
9
- const db = (this as any).db;
10
- const queryInterface = db.sequelize.getQueryInterface();
11
- const tablePrefix = db.options.tablePrefix || '';
12
-
13
- await this.addRunColumns(queryInterface, `${tablePrefix}agentLoopRuns`);
14
- await this.addStepColumns(queryInterface, `${tablePrefix}agentLoopSteps`);
15
- await this.addConfigColumns(queryInterface, `${tablePrefix}orchestratorConfig`);
16
- await this.ensureHarnessProfiles(queryInterface, `${tablePrefix}agentHarnessProfiles`);
17
- await this.seedDefaultProfiles();
18
- }
19
-
20
- async addRunColumns(queryInterface: any, tableName: string) {
21
- const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
22
- if (!tableExists) return;
23
- const tableDesc = await queryInterface.describeTable(tableName);
24
- const addIfMissing = async (name: string, spec: any) => {
25
- if (tableDesc[name]) return;
26
- await queryInterface.addColumn(tableName, name, spec);
27
- };
28
-
29
- await addIfMissing('approvalStatus', { type: DataTypes.STRING(30), allowNull: true, defaultValue: 'none' });
30
- await addIfMissing('approvedById', { type: DataTypes.BIGINT, allowNull: true });
31
- await addIfMissing('approvedAt', { type: DataTypes.DATE, allowNull: true });
32
- await addIfMissing('rejectionReason', { type: DataTypes.TEXT, allowNull: true });
33
- await addIfMissing('changeRequest', { type: DataTypes.TEXT, allowNull: true });
34
- await addIfMissing('planVersion', { type: DataTypes.INTEGER, allowNull: true, defaultValue: 1 });
35
- await addIfMissing('planSource', { type: DataTypes.STRING(50), allowNull: true });
36
- await addIfMissing('plannerModel', { type: DataTypes.STRING(100), allowNull: true });
37
- await addIfMissing('lockedBy', { type: DataTypes.STRING(100), allowNull: true });
38
- await addIfMissing('lockedUntil', { type: DataTypes.DATE, allowNull: true });
39
- }
40
-
41
- async addStepColumns(queryInterface: any, tableName: string) {
42
- const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
43
- if (!tableExists) return;
44
- const tableDesc = await queryInterface.describeTable(tableName);
45
- if (!tableDesc.dependencyPolicy) {
46
- await queryInterface.addColumn(tableName, 'dependencyPolicy', {
47
- type: DataTypes.STRING(30),
48
- allowNull: true,
49
- defaultValue: 'require_success',
50
- });
51
- }
52
- }
53
-
54
- async addConfigColumns(queryInterface: any, tableName: string) {
55
- const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
56
- if (!tableExists) return;
57
- const tableDesc = await queryInterface.describeTable(tableName);
58
- if (!tableDesc.harnessTag) {
59
- await queryInterface.addColumn(tableName, 'harnessTag', {
60
- type: DataTypes.STRING(100),
61
- allowNull: true,
62
- defaultValue: 'default',
63
- });
64
- }
65
- }
66
-
67
- async ensureHarnessProfiles(queryInterface: any, tableName: string) {
68
- const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
69
- if (tableExists) return;
70
- await queryInterface.createTable(tableName, {
71
- id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
72
- tag: { type: DataTypes.STRING(100), allowNull: false, unique: true },
73
- title: { type: DataTypes.STRING(200), allowNull: true },
74
- description: { type: DataTypes.TEXT, allowNull: true },
75
- enabled: { type: DataTypes.BOOLEAN, allowNull: true, defaultValue: true },
76
- settings: { type: DataTypes.JSON, allowNull: true, defaultValue: {} },
77
- createdAt: { type: DataTypes.DATE, allowNull: true },
78
- updatedAt: { type: DataTypes.DATE, allowNull: true },
79
- });
80
- }
81
-
82
- async seedDefaultProfiles() {
83
- const repo = (this as any).db.getRepository('agentHarnessProfiles');
84
- if (!repo) return;
85
- const profiles = [
86
- {
87
- tag: 'default',
88
- title: 'Default',
89
- description: 'Balanced profile for normal multi-agent work.',
90
- settings: {
91
- requirePlanApproval: true,
92
- allowSubAgents: true,
93
- allowToolCalls: true,
94
- maxParallelSubAgents: 3,
95
- maxControllerSteps: 100,
96
- },
97
- },
98
- {
99
- tag: 'safe',
100
- title: 'Safe',
101
- description: 'Strict approval-first profile for higher-risk work.',
102
- settings: {
103
- requirePlanApproval: true,
104
- allowSubAgents: true,
105
- allowToolCalls: true,
106
- maxParallelSubAgents: 1,
107
- maxControllerSteps: 50,
108
- requireVerification: true,
109
- },
110
- },
111
- {
112
- tag: 'file-heavy',
113
- title: 'File Heavy',
114
- description: 'Profile for tasks that inspect or transform many attachments/files.',
115
- settings: {
116
- requirePlanApproval: true,
117
- allowSubAgents: true,
118
- allowToolCalls: true,
119
- maxParallelSubAgents: 2,
120
- maxControllerSteps: 120,
121
- preferFileTools: true,
122
- },
123
- },
124
- ];
125
- for (const profile of profiles) {
126
- const existing = await repo.findOne({ filter: { tag: profile.tag } });
127
- if (existing) continue;
128
- await repo.create({
129
- values: {
130
- ...profile,
131
- enabled: true,
132
- createdAt: new Date(),
133
- updatedAt: new Date(),
134
- },
135
- });
136
- }
137
- }
138
-
139
- async down() {
140
- // No rollback: new nullable columns and the profile table are backward compatible.
141
- }
142
- }
1
+ import { DataTypes } from '@nocobase/database';
2
+ import { Migration } from '@nocobase/server';
3
+
4
+ export default class AddPlanApprovalAndHarnessProfiles extends Migration {
5
+ on = 'afterLoad';
6
+ appVersion = '>=0.1.0';
7
+
8
+ async up() {
9
+ const db = (this as any).db;
10
+ const queryInterface = db.sequelize.getQueryInterface();
11
+ const tablePrefix = db.options.tablePrefix || '';
12
+
13
+ await this.addRunColumns(queryInterface, `${tablePrefix}agentLoopRuns`);
14
+ await this.addStepColumns(queryInterface, `${tablePrefix}agentLoopSteps`);
15
+ await this.addConfigColumns(queryInterface, `${tablePrefix}orchestratorConfig`);
16
+ await this.ensureHarnessProfiles(queryInterface, `${tablePrefix}agentHarnessProfiles`);
17
+ await this.seedDefaultProfiles();
18
+ }
19
+
20
+ async addRunColumns(queryInterface: any, tableName: string) {
21
+ const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
22
+ if (!tableExists) return;
23
+ const tableDesc = await queryInterface.describeTable(tableName);
24
+ const addIfMissing = async (name: string, spec: any) => {
25
+ if (tableDesc[name]) return;
26
+ await queryInterface.addColumn(tableName, name, spec);
27
+ };
28
+
29
+ await addIfMissing('approvalStatus', { type: DataTypes.STRING(30), allowNull: true, defaultValue: 'none' });
30
+ await addIfMissing('approvedById', { type: DataTypes.BIGINT, allowNull: true });
31
+ await addIfMissing('approvedAt', { type: DataTypes.DATE, allowNull: true });
32
+ await addIfMissing('rejectionReason', { type: DataTypes.TEXT, allowNull: true });
33
+ await addIfMissing('changeRequest', { type: DataTypes.TEXT, allowNull: true });
34
+ await addIfMissing('planVersion', { type: DataTypes.INTEGER, allowNull: true, defaultValue: 1 });
35
+ await addIfMissing('planSource', { type: DataTypes.STRING(50), allowNull: true });
36
+ await addIfMissing('plannerModel', { type: DataTypes.STRING(100), allowNull: true });
37
+ await addIfMissing('lockedBy', { type: DataTypes.STRING(100), allowNull: true });
38
+ await addIfMissing('lockedUntil', { type: DataTypes.DATE, allowNull: true });
39
+ }
40
+
41
+ async addStepColumns(queryInterface: any, tableName: string) {
42
+ const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
43
+ if (!tableExists) return;
44
+ const tableDesc = await queryInterface.describeTable(tableName);
45
+ if (!tableDesc.dependencyPolicy) {
46
+ await queryInterface.addColumn(tableName, 'dependencyPolicy', {
47
+ type: DataTypes.STRING(30),
48
+ allowNull: true,
49
+ defaultValue: 'require_success',
50
+ });
51
+ }
52
+ }
53
+
54
+ async addConfigColumns(queryInterface: any, tableName: string) {
55
+ const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
56
+ if (!tableExists) return;
57
+ const tableDesc = await queryInterface.describeTable(tableName);
58
+ if (!tableDesc.harnessTag) {
59
+ await queryInterface.addColumn(tableName, 'harnessTag', {
60
+ type: DataTypes.STRING(100),
61
+ allowNull: true,
62
+ defaultValue: 'default',
63
+ });
64
+ }
65
+ }
66
+
67
+ async ensureHarnessProfiles(queryInterface: any, tableName: string) {
68
+ const tableExists = await queryInterface.tableExists(tableName).catch(() => false);
69
+ if (tableExists) return;
70
+ await queryInterface.createTable(tableName, {
71
+ id: { type: DataTypes.BIGINT, primaryKey: true, autoIncrement: true },
72
+ tag: { type: DataTypes.STRING(100), allowNull: false, unique: true },
73
+ title: { type: DataTypes.STRING(200), allowNull: true },
74
+ description: { type: DataTypes.TEXT, allowNull: true },
75
+ enabled: { type: DataTypes.BOOLEAN, allowNull: true, defaultValue: true },
76
+ settings: { type: DataTypes.JSON, allowNull: true, defaultValue: {} },
77
+ createdAt: { type: DataTypes.DATE, allowNull: true },
78
+ updatedAt: { type: DataTypes.DATE, allowNull: true },
79
+ });
80
+ }
81
+
82
+ async seedDefaultProfiles() {
83
+ const repo = (this as any).db.getRepository('agentHarnessProfiles');
84
+ if (!repo) return;
85
+ const profiles = [
86
+ {
87
+ tag: 'default',
88
+ title: 'Default',
89
+ description: 'Balanced profile for normal multi-agent work.',
90
+ settings: {
91
+ requirePlanApproval: true,
92
+ allowSubAgents: true,
93
+ allowToolCalls: true,
94
+ maxParallelSubAgents: 3,
95
+ maxControllerSteps: 100,
96
+ },
97
+ },
98
+ {
99
+ tag: 'safe',
100
+ title: 'Safe',
101
+ description: 'Strict approval-first profile for higher-risk work.',
102
+ settings: {
103
+ requirePlanApproval: true,
104
+ allowSubAgents: true,
105
+ allowToolCalls: true,
106
+ maxParallelSubAgents: 1,
107
+ maxControllerSteps: 50,
108
+ requireVerification: true,
109
+ },
110
+ },
111
+ {
112
+ tag: 'file-heavy',
113
+ title: 'File Heavy',
114
+ description: 'Profile for tasks that inspect or transform many attachments/files.',
115
+ settings: {
116
+ requirePlanApproval: true,
117
+ allowSubAgents: true,
118
+ allowToolCalls: true,
119
+ maxParallelSubAgents: 2,
120
+ maxControllerSteps: 120,
121
+ preferFileTools: true,
122
+ },
123
+ },
124
+ ];
125
+ for (const profile of profiles) {
126
+ const existing = await repo.findOne({ filter: { tag: profile.tag } });
127
+ if (existing) continue;
128
+ await repo.create({
129
+ values: {
130
+ ...profile,
131
+ enabled: true,
132
+ createdAt: new Date(),
133
+ updatedAt: new Date(),
134
+ },
135
+ });
136
+ }
137
+ }
138
+
139
+ async down() {
140
+ // No rollback: new nullable columns and the profile table are backward compatible.
141
+ }
142
+ }
@@ -0,0 +1,89 @@
1
+ import { Migration } from '@nocobase/server';
2
+
3
+ export default class AddTokenFieldsMigration extends Migration {
4
+ on = 'afterLoad';
5
+ appVersion = '<=2.x';
6
+
7
+ async up() {
8
+ const queryInterface = (this as any).db.sequelize.getQueryInterface();
9
+ const DataTypes = (this as any).db.sequelize.constructor['DataTypes'];
10
+ const prefix = (this as any).db.options.tablePrefix || '';
11
+
12
+ // ── agentExecutionSpans ──
13
+ const spansTable = `${prefix}agentExecutionSpans`;
14
+ const spansExists = await queryInterface
15
+ .describeTable(spansTable)
16
+ .then(() => true)
17
+ .catch(() => false);
18
+ if (spansExists) {
19
+ const spansCols = await queryInterface.describeTable(spansTable);
20
+ const addSpanIfMissing = async (name: string, definition: any) => {
21
+ if (!spansCols[name]) {
22
+ await queryInterface.addColumn(spansTable, name, definition);
23
+ }
24
+ };
25
+ await addSpanIfMissing('inputTokens', { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0 });
26
+ await addSpanIfMissing('outputTokens', { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0 });
27
+ await addSpanIfMissing('totalTokens', { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0 });
28
+ await addSpanIfMissing('cost', { type: DataTypes.FLOAT, allowNull: false, defaultValue: 0 });
29
+ }
30
+
31
+ // ── agentLoopRuns ──
32
+ const runsTable = `${prefix}agentLoopRuns`;
33
+ const runsExists = await queryInterface
34
+ .describeTable(runsTable)
35
+ .then(() => true)
36
+ .catch(() => false);
37
+ if (runsExists) {
38
+ const runsCols = await queryInterface.describeTable(runsTable);
39
+ const addRunIfMissing = async (name: string, definition: any) => {
40
+ if (!runsCols[name]) {
41
+ await queryInterface.addColumn(runsTable, name, definition);
42
+ }
43
+ };
44
+ await addRunIfMissing('totalInputTokens', { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0 });
45
+ await addRunIfMissing('totalOutputTokens', { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0 });
46
+ await addRunIfMissing('totalTokens', { type: DataTypes.INTEGER, allowNull: false, defaultValue: 0 });
47
+ await addRunIfMissing('totalCost', { type: DataTypes.FLOAT, allowNull: false, defaultValue: 0 });
48
+ await addRunIfMissing('budgetMaxTokens', { type: DataTypes.INTEGER, allowNull: true });
49
+ await addRunIfMissing('budgetMaxCost', { type: DataTypes.FLOAT, allowNull: true });
50
+ }
51
+
52
+ // ── orchestratorConfig ──
53
+ const configTable = `${prefix}orchestratorConfig`;
54
+ const configExists = await queryInterface
55
+ .describeTable(configTable)
56
+ .then(() => true)
57
+ .catch(() => false);
58
+ if (configExists) {
59
+ const configCols = await queryInterface.describeTable(configTable);
60
+ const addConfigIfMissing = async (name: string, definition: any) => {
61
+ if (!configCols[name]) {
62
+ await queryInterface.addColumn(configTable, name, definition);
63
+ }
64
+ };
65
+ await addConfigIfMissing('budgetMaxTokens', { type: DataTypes.INTEGER, allowNull: true, defaultValue: 0 });
66
+ await addConfigIfMissing('budgetMaxCost', { type: DataTypes.FLOAT, allowNull: true, defaultValue: 0 });
67
+ }
68
+ }
69
+
70
+ async down() {
71
+ const queryInterface = (this as any).db.sequelize.getQueryInterface();
72
+ const prefix = (this as any).db.options.tablePrefix || '';
73
+
74
+ const tables = [
75
+ { name: `${prefix}agentExecutionSpans`, cols: ['inputTokens', 'outputTokens', 'totalTokens', 'cost'] },
76
+ {
77
+ name: `${prefix}agentLoopRuns`,
78
+ cols: ['totalInputTokens', 'totalOutputTokens', 'totalTokens', 'totalCost', 'budgetMaxTokens', 'budgetMaxCost'],
79
+ },
80
+ { name: `${prefix}orchestratorConfig`, cols: ['budgetMaxTokens', 'budgetMaxCost'] },
81
+ ];
82
+
83
+ for (const { name, cols } of tables) {
84
+ for (const col of cols) {
85
+ await queryInterface.removeColumn(name, col).catch(() => {});
86
+ }
87
+ }
88
+ }
89
+ }
@@ -5,6 +5,7 @@ import { createExternalRagSearchTool } from './tools/external-rag-search';
5
5
  import { createOrchestratorPlanTools } from './tools/orchestrator-plan';
6
6
  import { registerTracingResource } from './resources/tracing';
7
7
  import { registerAgentLoopResource } from './resources/agent-loop';
8
+ import { getRunEventBus } from './services/RunEventBus';
8
9
  import SkillHubSubFeature from './skill-hub/plugin';
9
10
  import { AgentLoopService } from './services/AgentLoopService';
10
11
 
@@ -41,6 +42,7 @@ export class PluginAgentOrchestratorServer extends Plugin {
41
42
  'agentLoopRuns:*',
42
43
  'agentLoopSteps:*',
43
44
  'agentLoopEvents:*',
45
+ 'agentLoopEventsStream:*',
44
46
  'agentHarnessProfiles:*',
45
47
  'agentExecutionSpans:*',
46
48
  'skillDefinitions:*',
@@ -51,6 +53,21 @@ export class PluginAgentOrchestratorServer extends Plugin {
51
53
  ],
52
54
  });
53
55
 
56
+ // Allow any logged-in user to read available skills and loop configs.
57
+ // This mirrors the plugin-ai pattern (acl.allow with 'loggedIn')
58
+ // so that non-admin users with AI roles can use skills without
59
+ // requiring manual snippet assignment per role.
60
+ // Create/update/destroy remain restricted to admin roles via the snippet above.
61
+ (this as any).app.acl.allow('skillDefinitions', 'list', 'loggedIn');
62
+ (this as any).app.acl.allow('skillDefinitions', 'get', 'loggedIn');
63
+ (this as any).app.acl.allow('skillLoopConfigs', 'list', 'loggedIn');
64
+ (this as any).app.acl.allow('skillLoopConfigs', 'get', 'loggedIn');
65
+ (this as any).app.acl.allow('skillExecutions', 'list', 'loggedIn');
66
+ (this as any).app.acl.allow('skillExecutions', 'get', 'loggedIn');
67
+ (this as any).app.acl.allow('skillHub', 'test', 'loggedIn');
68
+ (this as any).app.acl.allow('skillHub', 'download', 'loggedIn');
69
+ (this as any).app.acl.allow('skillHub', 'listTemplates', 'loggedIn');
70
+
54
71
  // --- Register Dynamic Tools ---
55
72
  // Each configured sub-agent becomes a callable tool for its leader.
56
73
  // Uses createReactAgent (LangGraph public API) instead of private AIEmployee class.
@@ -63,6 +80,57 @@ export class PluginAgentOrchestratorServer extends Plugin {
63
80
  // --- Register Agent Loop Resource ---
64
81
  registerAgentLoopResource(this, this.agentLoopService);
65
82
 
83
+ // --- Register SSE Event Stream Resource (Phase 6) ---
84
+ (this as any).app.resource({
85
+ name: 'agentLoopEventsStream',
86
+ actions: {
87
+ async stream(ctx, next) {
88
+ const runId = ctx.action.params?.runId || ctx.query?.runId || ctx.request.query?.runId;
89
+ if (!runId) {
90
+ ctx.throw(400, 'runId query parameter is required.');
91
+ return;
92
+ }
93
+
94
+ ctx.type = 'text/event-stream';
95
+ ctx.set('Cache-Control', 'no-cache');
96
+ ctx.set('Connection', 'keep-alive');
97
+ ctx.set('X-Accel-Buffering', 'no');
98
+
99
+ const unsubscribe = getRunEventBus().subscribe(runId, (event: any) => {
100
+ try {
101
+ ctx.res.write(`data: ${JSON.stringify(event)}\n\n`);
102
+ } catch {
103
+ unsubscribe();
104
+ }
105
+ });
106
+
107
+ const keepalive = setInterval(() => {
108
+ try {
109
+ ctx.res.write(': keepalive\n\n');
110
+ } catch {
111
+ clearInterval(keepalive);
112
+ unsubscribe();
113
+ }
114
+ }, 15000);
115
+
116
+ ctx.req.on('close', () => {
117
+ clearInterval(keepalive);
118
+ unsubscribe();
119
+ });
120
+
121
+ ctx.req.on('error', () => {
122
+ clearInterval(keepalive);
123
+ unsubscribe();
124
+ });
125
+
126
+ ctx.res.writeHead(200);
127
+ ctx.res.write(': connected\n\n');
128
+
129
+ await next();
130
+ },
131
+ },
132
+ });
133
+
66
134
  // --- Register Tracing Resource (Phase 5) ---
67
135
  // Custom read-only resource for the Swarm Tracing admin page.
68
136
  registerTracingResource(this);
@@ -1,17 +1,6 @@
1
1
  import { Plugin } from '@nocobase/server';
2
2
  import { AgentLoopService } from '../services/AgentLoopService';
3
-
4
- function toPlain(record: any) {
5
- return record?.toJSON?.() || record;
6
- }
7
-
8
- function currentUserId(ctx: any) {
9
- return ctx?.state?.currentUser?.id || ctx?.auth?.user?.id;
10
- }
11
-
12
- function values(ctx: any) {
13
- return ctx.request?.body || ctx.action?.params?.values || {};
14
- }
3
+ import { toPlain, currentUserId, valuesFromCtx as values } from '../utils/ctx-utils';
15
4
 
16
5
  function formatRunRow(raw: any) {
17
6
  const row = toPlain(raw);
@@ -178,6 +167,26 @@ export function registerAgentLoopResource(plugin: Plugin, service: AgentLoopServ
178
167
  };
179
168
  await next();
180
169
  },
170
+
171
+ async stepFeedback(ctx, next) {
172
+ const body = values(ctx);
173
+ if (!body.stepId || !body.rating) {
174
+ ctx.throw(400, 'stepId and rating are required');
175
+ return;
176
+ }
177
+ if (!['positive', 'negative'].includes(body.rating)) {
178
+ ctx.throw(400, 'rating must be "positive" or "negative"');
179
+ return;
180
+ }
181
+ ctx.body = {
182
+ data: await service.stepFeedback(
183
+ body.stepId,
184
+ { rating: body.rating, comment: body.comment, category: body.category },
185
+ { userId: currentUserId(ctx) },
186
+ ),
187
+ };
188
+ await next();
189
+ },
181
190
  },
182
191
  });
183
192
  }
@@ -1,8 +1,6 @@
1
1
  import { Plugin } from '@nocobase/server';
2
2
 
3
- function toPlain(row: any) {
4
- return row?.toJSON?.() || row;
5
- }
3
+ import { toPlain } from '../utils/ctx-utils';
6
4
 
7
5
  function normalizeSpanFilter(filter: any = {}) {
8
6
  const next = { ...filter };
@@ -68,8 +66,7 @@ function buildSpanTree(rows: any[]) {
68
66
  const sortTree = (items: any[]) => {
69
67
  items.sort(
70
68
  (a, b) =>
71
- new Date(a.startedAt || a.createdAt || 0).getTime() -
72
- new Date(b.startedAt || b.createdAt || 0).getTime(),
69
+ new Date(a.startedAt || a.createdAt || 0).getTime() - new Date(b.startedAt || b.createdAt || 0).getTime(),
73
70
  );
74
71
  for (const item of items) sortTree(item.children || []);
75
72
  };
@@ -82,8 +79,7 @@ function flattenSpanTimeline(rows: any[]) {
82
79
  .map(toPlain)
83
80
  .sort(
84
81
  (a, b) =>
85
- new Date(a.startedAt || a.createdAt || 0).getTime() -
86
- new Date(b.startedAt || b.createdAt || 0).getTime(),
82
+ new Date(a.startedAt || a.createdAt || 0).getTime() - new Date(b.startedAt || b.createdAt || 0).getTime(),
87
83
  )
88
84
  .map((row) => {
89
85
  const input = row.input || {};