plugin-agent-orchestrator 1.0.20 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/hooks/useRunEventStream.d.ts +22 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -1
- package/dist/externalVersion.js +6 -6
- package/dist/server/collections/agent-execution-spans.js +24 -0
- package/dist/server/collections/agent-loop-runs.js +36 -0
- package/dist/server/collections/orchestrator-config.js +14 -0
- package/dist/server/migrations/20260601000000-add-token-fields.d.ts +7 -0
- package/dist/server/migrations/20260601000000-add-token-fields.js +101 -0
- package/dist/server/plugin.js +47 -0
- package/dist/server/resources/agent-loop.js +33 -25
- package/dist/server/resources/tracing.js +5 -8
- package/dist/server/services/AgentHarness.d.ts +2 -0
- package/dist/server/services/AgentHarness.js +56 -90
- package/dist/server/services/AgentLoopController.d.ts +33 -20
- package/dist/server/services/AgentLoopController.js +164 -125
- package/dist/server/services/AgentLoopRepository.js +16 -34
- package/dist/server/services/AgentLoopService.d.ts +28 -18
- package/dist/server/services/AgentLoopService.js +7 -1
- package/dist/server/services/AgentPlannerService.js +5 -25
- package/dist/server/services/AgentRegistryService.d.ts +8 -0
- package/dist/server/services/AgentRegistryService.js +34 -24
- package/dist/server/services/CircuitBreaker.d.ts +40 -0
- package/dist/server/services/CircuitBreaker.js +120 -0
- package/dist/server/services/ContextAggregator.d.ts +45 -0
- package/dist/server/services/ContextAggregator.js +201 -0
- package/dist/server/services/ExecutionSpanService.js +2 -5
- package/dist/server/services/RunEventBus.d.ts +9 -0
- package/dist/server/services/RunEventBus.js +73 -0
- package/dist/server/services/TokenTracker.d.ts +62 -0
- package/dist/server/services/TokenTracker.js +173 -0
- package/dist/server/tools/agent-loop.d.ts +8 -8
- package/dist/server/tools/agent-loop.js +30 -63
- package/dist/server/tools/delegate-task.js +14 -72
- package/dist/server/tools/orchestrator-plan.d.ts +6 -6
- package/dist/server/tools/orchestrator-plan.js +10 -47
- package/dist/server/types.d.ts +47 -0
- package/dist/server/types.js +24 -0
- package/dist/server/utils/ctx-utils.d.ts +30 -0
- package/dist/server/utils/ctx-utils.js +152 -0
- package/dist/server/utils/logging.d.ts +6 -0
- package/dist/server/utils/logging.js +86 -0
- package/package.json +44 -44
- package/src/client/AgentRunsTab.tsx +764 -764
- package/src/client/HarnessProfilesTab.tsx +247 -247
- package/src/client/OrchestratorSettings.tsx +106 -106
- package/src/client/RulesTab.tsx +716 -716
- package/src/client/hooks/useRunEventStream.ts +76 -0
- package/src/client/index.tsx +2 -1
- package/src/client/plugin.tsx +27 -27
- package/src/client/skill-hub/components/LoopSettings.tsx +331 -331
- package/src/client/skill-hub/index.tsx +51 -51
- package/src/client/skill-hub/tools/InteractionSchemasProvider.tsx +99 -99
- package/src/client/skill-hub/tools/SkillHubCard.tsx +109 -109
- package/src/client/skill-hub/tools/loopTemplates.ts +52 -52
- package/src/client/skill-hub/tools/registerSkillLoopCards.ts +58 -58
- package/src/client/tools/PlanApprovalCard.tsx +175 -175
- package/src/client/tools/registerOrchestratorCards.ts +7 -7
- package/src/server/__tests__/agent-loop-controller.test.ts +375 -0
- package/src/server/__tests__/circuit-breaker.test.ts +169 -0
- package/src/server/__tests__/context-aggregator.test.ts +222 -0
- package/src/server/__tests__/parallel-execution.test.ts +318 -0
- package/src/server/__tests__/smoke.test.ts +120 -0
- package/src/server/collections/agent-execution-spans.ts +24 -0
- package/src/server/collections/agent-harness-profiles.ts +59 -59
- package/src/server/collections/agent-loop-events.ts +71 -71
- package/src/server/collections/agent-loop-runs.ts +38 -1
- package/src/server/collections/agent-loop-steps.ts +144 -144
- package/src/server/collections/orchestrator-config.ts +14 -0
- package/src/server/collections/skill-executions.ts +106 -106
- package/src/server/collections/skill-loop-configs.ts +65 -65
- package/src/server/migrations/20260524000000-add-agent-loop-fields-to-skill-executions.ts +30 -30
- package/src/server/migrations/20260524001000-add-plan-approval-and-harness-profiles.ts +142 -142
- package/src/server/migrations/20260601000000-add-token-fields.ts +89 -0
- package/src/server/plugin.ts +53 -0
- package/src/server/resources/agent-loop.ts +21 -12
- package/src/server/resources/tracing.ts +3 -7
- package/src/server/services/AgentHarness.ts +78 -116
- package/src/server/services/AgentLoopController.ts +197 -122
- package/src/server/services/AgentLoopRepository.ts +9 -25
- package/src/server/services/AgentLoopService.ts +13 -1
- package/src/server/services/AgentPlanValidator.ts +73 -73
- package/src/server/services/AgentPlannerService.ts +2 -25
- package/src/server/services/AgentRegistryService.ts +40 -31
- package/src/server/services/CircuitBreaker.ts +116 -0
- package/src/server/services/ContextAggregator.ts +239 -0
- package/src/server/services/ExecutionSpanService.ts +2 -4
- package/src/server/services/RunEventBus.ts +45 -0
- package/src/server/services/TokenTracker.ts +209 -0
- package/src/server/skill-hub/plugin.ts +898 -898
- package/src/server/skill-hub/tasks/SkillExecutionTask.ts +460 -460
- package/src/server/tools/agent-loop.ts +18 -57
- package/src/server/tools/delegate-task.ts +11 -93
- package/src/server/tools/orchestrator-plan.ts +26 -50
- package/src/server/tools/skill-execute.ts +160 -160
- package/src/server/types.ts +55 -0
- package/src/server/utils/ctx-utils.ts +118 -0
- package/src/server/utils/logging.ts +63 -0
|
@@ -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
|
+
}
|
package/src/server/plugin.ts
CHANGED
|
@@ -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:*',
|
|
@@ -63,6 +65,57 @@ export class PluginAgentOrchestratorServer extends Plugin {
|
|
|
63
65
|
// --- Register Agent Loop Resource ---
|
|
64
66
|
registerAgentLoopResource(this, this.agentLoopService);
|
|
65
67
|
|
|
68
|
+
// --- Register SSE Event Stream Resource (Phase 6) ---
|
|
69
|
+
(this as any).app.resource({
|
|
70
|
+
name: 'agentLoopEventsStream',
|
|
71
|
+
actions: {
|
|
72
|
+
async stream(ctx, next) {
|
|
73
|
+
const runId = ctx.action.params?.runId || ctx.query?.runId || ctx.request.query?.runId;
|
|
74
|
+
if (!runId) {
|
|
75
|
+
ctx.throw(400, 'runId query parameter is required.');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ctx.type = 'text/event-stream';
|
|
80
|
+
ctx.set('Cache-Control', 'no-cache');
|
|
81
|
+
ctx.set('Connection', 'keep-alive');
|
|
82
|
+
ctx.set('X-Accel-Buffering', 'no');
|
|
83
|
+
|
|
84
|
+
const unsubscribe = getRunEventBus().subscribe(runId, (event: any) => {
|
|
85
|
+
try {
|
|
86
|
+
ctx.res.write(`data: ${JSON.stringify(event)}\n\n`);
|
|
87
|
+
} catch {
|
|
88
|
+
unsubscribe();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const keepalive = setInterval(() => {
|
|
93
|
+
try {
|
|
94
|
+
ctx.res.write(': keepalive\n\n');
|
|
95
|
+
} catch {
|
|
96
|
+
clearInterval(keepalive);
|
|
97
|
+
unsubscribe();
|
|
98
|
+
}
|
|
99
|
+
}, 15000);
|
|
100
|
+
|
|
101
|
+
ctx.req.on('close', () => {
|
|
102
|
+
clearInterval(keepalive);
|
|
103
|
+
unsubscribe();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
ctx.req.on('error', () => {
|
|
107
|
+
clearInterval(keepalive);
|
|
108
|
+
unsubscribe();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
ctx.res.writeHead(200);
|
|
112
|
+
ctx.res.write(': connected\n\n');
|
|
113
|
+
|
|
114
|
+
await next();
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
66
119
|
// --- Register Tracing Resource (Phase 5) ---
|
|
67
120
|
// Custom read-only resource for the Swarm Tracing admin page.
|
|
68
121
|
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
|
-
|
|
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 || {};
|