@memberjunction/ai-agent-manager 2.108.0 → 2.110.0
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +35 -0
- package/dist/agent-spec-sync.d.ts +117 -7
- package/dist/agent-spec-sync.d.ts.map +1 -1
- package/dist/agent-spec-sync.js +599 -50
- package/dist/agent-spec-sync.js.map +1 -1
- package/dist/agents/architect-agent.d.ts +52 -0
- package/dist/agents/architect-agent.d.ts.map +1 -0
- package/dist/agents/architect-agent.js +384 -0
- package/dist/agents/architect-agent.js.map +1 -0
- package/dist/agents/builder-agent.d.ts +31 -0
- package/dist/agents/builder-agent.d.ts.map +1 -0
- package/dist/agents/builder-agent.js +131 -0
- package/dist/agents/builder-agent.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/old/agent-definition.interface.d.ts +64 -0
- package/dist/old/agent-definition.interface.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/agent-spec-sync.ts +784 -56
- package/src/agents/architect-agent.ts +447 -0
- package/src/agents/builder-agent.ts +151 -0
- package/src/index.ts +21 -2
- package/src/old/agent-definition.interface.ts +59 -0
- package/tsconfig.json +3 -1
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { BaseAgent, PayloadManager } from '@memberjunction/ai-agents';
|
|
2
|
+
import { ExecuteAgentParams, BaseAgentNextStep, AgentSpec } from '@memberjunction/ai-core-plus';
|
|
3
|
+
import { AIAgentRunEntityExtended, AIAgentRunStepEntityExtended, ActionEntity, AIPromptEntity } from '@memberjunction/core-entities';
|
|
4
|
+
import { Metadata, RunView } from '@memberjunction/core';
|
|
5
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Architect Agent - Transforms technical design into validated AgentSpec JSON
|
|
9
|
+
*
|
|
10
|
+
* This agent creates AgentSpec objects from technical designs and validates them
|
|
11
|
+
* before passing to the Builder Agent for persistence. It overrides validateSuccessNextStep
|
|
12
|
+
* to ensure the generated AgentSpec is valid before proceeding.
|
|
13
|
+
*
|
|
14
|
+
* Key responsibilities:
|
|
15
|
+
* - Transform design documents into AgentSpec format
|
|
16
|
+
* - Validate required fields are present
|
|
17
|
+
* - Verify action IDs exist in database
|
|
18
|
+
* - Verify prompt IDs exist (if specified)
|
|
19
|
+
* - Validate SubAgent structure
|
|
20
|
+
* - Auto-correct minor issues (casing, missing optional fields)
|
|
21
|
+
* - Force retry with detailed errors if validation fails
|
|
22
|
+
*/
|
|
23
|
+
@RegisterClass(BaseAgent, 'AgentArchitectAgent')
|
|
24
|
+
export class AgentArchitectAgent extends BaseAgent {
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Applies payload changes if any are requested in the next step
|
|
28
|
+
*/
|
|
29
|
+
protected applyPayloadChanges<P>(payload: P, nextStep: BaseAgentNextStep<P>): P {
|
|
30
|
+
if (nextStep.payloadChangeRequest) {
|
|
31
|
+
const pm = new PayloadManager();
|
|
32
|
+
const result = pm.applyAgentChangeRequest<P>(payload, nextStep.payloadChangeRequest);
|
|
33
|
+
return result.result || payload;
|
|
34
|
+
}
|
|
35
|
+
return payload;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected override async validateSuccessNextStep<P>(
|
|
39
|
+
params: ExecuteAgentParams,
|
|
40
|
+
nextStep: BaseAgentNextStep<P>,
|
|
41
|
+
currentPayload: P,
|
|
42
|
+
agentRun: AIAgentRunEntityExtended,
|
|
43
|
+
currentStep: AIAgentRunStepEntityExtended
|
|
44
|
+
): Promise<BaseAgentNextStep<P>> {
|
|
45
|
+
// First call base validation
|
|
46
|
+
const baseValidation = await super.validateSuccessNextStep(params, nextStep, currentPayload, agentRun, currentStep);
|
|
47
|
+
if (baseValidation.step === 'Retry') {
|
|
48
|
+
return baseValidation;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Apply payload changes to get the spec that would be sent forward
|
|
52
|
+
const payloadWithChanges = this.applyPayloadChanges<P>(currentPayload, nextStep);
|
|
53
|
+
|
|
54
|
+
// The payload IS the AgentSpec
|
|
55
|
+
const agentSpec = payloadWithChanges as AgentSpec;
|
|
56
|
+
|
|
57
|
+
console.log('🏗️ Architect Agent: Validating AgentSpec:', agentSpec ? `Name="${agentSpec.Name}"` : 'agentSpec is null/undefined');
|
|
58
|
+
|
|
59
|
+
// Validate the AgentSpec
|
|
60
|
+
const validation = await this.validateAgentSpec(agentSpec, params);
|
|
61
|
+
|
|
62
|
+
if (validation.errors.length > 0) {
|
|
63
|
+
console.log('❌ Architect Agent: Validation failed with errors:', validation.errors);
|
|
64
|
+
// Return retry with detailed error messages
|
|
65
|
+
return {
|
|
66
|
+
...nextStep,
|
|
67
|
+
step: 'Retry',
|
|
68
|
+
retryInstructions: this.formatValidationErrors(validation.errors),
|
|
69
|
+
newPayload: nextStep.newPayload
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// If spec was auto-corrected, update the payload with corrected spec
|
|
74
|
+
if (validation.correctedSpec) {
|
|
75
|
+
console.log('✓ Architect Agent: Auto-corrected AgentSpec, updating payload');
|
|
76
|
+
return {
|
|
77
|
+
...nextStep,
|
|
78
|
+
newPayload: validation.correctedSpec as P
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log('✅ Architect Agent: AgentSpec validation passed');
|
|
83
|
+
return nextStep;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Validates an AgentSpec object
|
|
88
|
+
* Returns errors array and potentially a corrected spec
|
|
89
|
+
*/
|
|
90
|
+
protected async validateAgentSpec(
|
|
91
|
+
spec: AgentSpec,
|
|
92
|
+
params: ExecuteAgentParams
|
|
93
|
+
): Promise<{ errors: string[]; correctedSpec?: AgentSpec }> {
|
|
94
|
+
const errors: string[] = [];
|
|
95
|
+
let corrected = false;
|
|
96
|
+
|
|
97
|
+
// Check if spec exists
|
|
98
|
+
if (!spec) {
|
|
99
|
+
errors.push('❌ AgentSpec is null or undefined. Ensure you place the AgentSpec in payload.agentSpec (inside a payloadChangeRequest with newElements or replaceElements)');
|
|
100
|
+
return { errors };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const correctedSpec = { ...spec };
|
|
104
|
+
|
|
105
|
+
// 1. Validate required fields
|
|
106
|
+
if (!correctedSpec.Name || correctedSpec.Name.trim().length === 0) {
|
|
107
|
+
errors.push('❌ AgentSpec.Name is required and cannot be empty');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!correctedSpec.StartingPayloadValidationMode) {
|
|
111
|
+
// Auto-correct: add default
|
|
112
|
+
correctedSpec.StartingPayloadValidationMode = 'Fail';
|
|
113
|
+
corrected = true;
|
|
114
|
+
console.log('✓ Auto-corrected: Added default StartingPayloadValidationMode = "Fail"');
|
|
115
|
+
} else if (correctedSpec.StartingPayloadValidationMode !== 'Fail' && correctedSpec.StartingPayloadValidationMode !== 'Warn') {
|
|
116
|
+
errors.push(`❌ StartingPayloadValidationMode must be either "Fail" or "Warn", got: "${correctedSpec.StartingPayloadValidationMode}"`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 2. Validate Actions if specified
|
|
120
|
+
if (correctedSpec.Actions && correctedSpec.Actions.length > 0) {
|
|
121
|
+
const actionValidation = await this.validateActions(correctedSpec.Actions, params);
|
|
122
|
+
if (actionValidation.errors.length > 0) {
|
|
123
|
+
errors.push(...actionValidation.errors);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 3. Validate SubAgents if specified
|
|
128
|
+
if (correctedSpec.SubAgents && correctedSpec.SubAgents.length > 0) {
|
|
129
|
+
const subAgentValidation = this.validateSubAgents(correctedSpec.SubAgents);
|
|
130
|
+
if (subAgentValidation.errors.length > 0) {
|
|
131
|
+
errors.push(...subAgentValidation.errors);
|
|
132
|
+
}
|
|
133
|
+
if (subAgentValidation.corrected) {
|
|
134
|
+
corrected = true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// 4. Validate TypeID and Status
|
|
139
|
+
const specWithType = correctedSpec as any;
|
|
140
|
+
if (!specWithType.TypeID) {
|
|
141
|
+
errors.push('❌ TypeID is required - must be "@lookup:MJ: AI Agent Types.Name=Loop" or "@lookup:MJ: AI Agent Types.Name=Flow"');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!specWithType.Status) {
|
|
145
|
+
// Auto-correct: add default
|
|
146
|
+
specWithType.Status = 'Active';
|
|
147
|
+
corrected = true;
|
|
148
|
+
console.log('✓ Auto-corrected: Added default Status = "Active"');
|
|
149
|
+
} else if (!['Active', 'Inactive', 'Pending'].includes(specWithType.Status)) {
|
|
150
|
+
errors.push(`❌ Status must be "Active", "Inactive", or "Pending", got: "${specWithType.Status}"`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 5. Validate agent type-specific requirements
|
|
154
|
+
const isLoopAgent = specWithType.TypeID?.includes('Loop');
|
|
155
|
+
const isFlowAgent = specWithType.TypeID?.includes('Flow');
|
|
156
|
+
|
|
157
|
+
if (isLoopAgent) {
|
|
158
|
+
// Loop agents require at least one prompt
|
|
159
|
+
if (!correctedSpec.Prompts || correctedSpec.Prompts.length === 0) {
|
|
160
|
+
errors.push('❌ Loop agents require at least ONE prompt in the Prompts array');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (isFlowAgent) {
|
|
165
|
+
// CRITICAL: Flow agents MUST have empty Prompts array (no agent-level prompts)
|
|
166
|
+
if (correctedSpec.Prompts && correctedSpec.Prompts.length > 0) {
|
|
167
|
+
errors.push('❌ Flow agents MUST have empty Prompts array. If LLM is needed, use a Prompt step OR a Loop sub-agent instead of agent-level prompts.');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Flow agents require Steps and Paths
|
|
171
|
+
if (!correctedSpec.Steps || correctedSpec.Steps.length === 0) {
|
|
172
|
+
errors.push('❌ Flow agents require at least ONE step in the Steps array');
|
|
173
|
+
}
|
|
174
|
+
// Paths are technically optional (single-step flows), but validate if present
|
|
175
|
+
if (correctedSpec.Steps && correctedSpec.Steps.length > 1) {
|
|
176
|
+
if (!correctedSpec.Paths || correctedSpec.Paths.length === 0) {
|
|
177
|
+
errors.push('❌ Flow agents with multiple steps require at least ONE path in the Paths array');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Validate that at least one step is marked as StartingStep
|
|
182
|
+
if (correctedSpec.Steps && correctedSpec.Steps.length > 0) {
|
|
183
|
+
const hasStartingStep = correctedSpec.Steps.some(step => step.StartingStep === true);
|
|
184
|
+
if (!hasStartingStep) {
|
|
185
|
+
errors.push('❌ Flow agents require at least ONE step with StartingStep: true');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Validate each step based on its type
|
|
189
|
+
for (let i = 0; i < correctedSpec.Steps.length; i++) {
|
|
190
|
+
const step = correctedSpec.Steps[i];
|
|
191
|
+
|
|
192
|
+
// Validate Action steps
|
|
193
|
+
if (step.StepType === 'Action') {
|
|
194
|
+
if (!step.ActionID) {
|
|
195
|
+
errors.push(`❌ Action step "${step.Name}" (index ${i}) must have ActionID field`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Validate ActionInputMapping if provided (supports both string and object)
|
|
199
|
+
if (step.ActionInputMapping) {
|
|
200
|
+
try {
|
|
201
|
+
if (typeof step.ActionInputMapping === 'string') {
|
|
202
|
+
JSON.parse(step.ActionInputMapping);
|
|
203
|
+
}
|
|
204
|
+
// else it's already an object, which is valid and will be stringified later
|
|
205
|
+
} catch (e: any) {
|
|
206
|
+
errors.push(`❌ Step "${step.Name}" (index ${i}) has invalid ActionInputMapping JSON: ${e?.message || String(e)}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Validate ActionOutputMapping if provided (supports both string and object)
|
|
211
|
+
if (step.ActionOutputMapping) {
|
|
212
|
+
try {
|
|
213
|
+
if (typeof step.ActionOutputMapping === 'string') {
|
|
214
|
+
JSON.parse(step.ActionOutputMapping);
|
|
215
|
+
}
|
|
216
|
+
// else it's already an object, which is valid and will be stringified later
|
|
217
|
+
} catch (e: any) {
|
|
218
|
+
errors.push(`❌ Step "${step.Name}" (index ${i}) has invalid ActionOutputMapping JSON: ${e?.message || String(e)}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Validate Prompt steps
|
|
224
|
+
if (step.StepType === 'Prompt') {
|
|
225
|
+
// If PromptID is empty or not provided, PromptText is required for inline creation
|
|
226
|
+
if (!step.PromptID || step.PromptID === '') {
|
|
227
|
+
if (!step.PromptText || step.PromptText.trim() === '') {
|
|
228
|
+
errors.push(`❌ Prompt step "${step.Name}" (index ${i}) has empty PromptID but missing PromptText. For inline prompt creation, PromptText is required.`);
|
|
229
|
+
}
|
|
230
|
+
// PromptName and PromptDescription are recommended but not required
|
|
231
|
+
if (!step.PromptName) {
|
|
232
|
+
console.log(`⚠️ Warning: Prompt step "${step.Name}" (index ${i}) is missing PromptName (will default to "[Step Name] Prompt")`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// If PromptID is provided (existing prompt), other fields are optional
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Validate Sub-Agent steps
|
|
239
|
+
if (step.StepType === 'Sub-Agent') {
|
|
240
|
+
// SubAgentID can be empty "" for new sub-agents (will be linked by name matching)
|
|
241
|
+
// No validation needed here - linking happens in AgentSpecSync
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 6. Validate optional but important fields
|
|
248
|
+
if (correctedSpec.FinalPayloadValidationMode &&
|
|
249
|
+
!['Fail', 'Retry', 'Warn'].includes(correctedSpec.FinalPayloadValidationMode)) {
|
|
250
|
+
errors.push(`❌ FinalPayloadValidationMode must be "Fail", "Retry", or "Warn", got: "${correctedSpec.FinalPayloadValidationMode}"`);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (correctedSpec.InvocationMode &&
|
|
254
|
+
!['Any', 'Agent', 'User', 'Never'].includes(correctedSpec.InvocationMode)) {
|
|
255
|
+
errors.push(`❌ InvocationMode must be "Any", "Agent", "User", or "Never", got: "${correctedSpec.InvocationMode}"`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
errors,
|
|
260
|
+
correctedSpec: corrected ? correctedSpec : undefined
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Validates that action IDs exist in the database
|
|
266
|
+
*/
|
|
267
|
+
protected async validateActions(
|
|
268
|
+
actions: AgentSpec['Actions'],
|
|
269
|
+
params: ExecuteAgentParams
|
|
270
|
+
): Promise<{ errors: string[] }> {
|
|
271
|
+
const errors: string[] = [];
|
|
272
|
+
|
|
273
|
+
if (!actions || actions.length === 0) {
|
|
274
|
+
return { errors };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const rv = new RunView();
|
|
279
|
+
const actionIds = actions
|
|
280
|
+
.map(a => a.ActionID)
|
|
281
|
+
.filter(id => id && id.trim().length > 0);
|
|
282
|
+
|
|
283
|
+
if (actionIds.length === 0) {
|
|
284
|
+
errors.push('❌ All actions must have an ActionID');
|
|
285
|
+
return { errors };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Query database for these action IDs
|
|
289
|
+
const filter = actionIds.map(id => `ID='${id}'`).join(' OR ');
|
|
290
|
+
const result = await rv.RunView<ActionEntity>({
|
|
291
|
+
EntityName: 'Actions',
|
|
292
|
+
ExtraFilter: filter,
|
|
293
|
+
ResultType: 'entity_object'
|
|
294
|
+
}, params.contextUser);
|
|
295
|
+
|
|
296
|
+
if (!result.Success) {
|
|
297
|
+
errors.push(`❌ Failed to validate actions: ${result.ErrorMessage}`);
|
|
298
|
+
return { errors };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const foundIds = new Set(result.Results.map(a => a.ID));
|
|
302
|
+
|
|
303
|
+
// Check which actions weren't found
|
|
304
|
+
for (const action of actions) {
|
|
305
|
+
if (!action.ActionID) {
|
|
306
|
+
errors.push('❌ Action is missing ActionID field');
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (!foundIds.has(action.ActionID)) {
|
|
311
|
+
errors.push(`❌ Action with ID "${action.ActionID}" not found in database. Please use "Find Best Action" or "List Actions" to get valid action IDs.`);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
} catch (error: any) {
|
|
316
|
+
errors.push(`❌ Error validating actions: ${error?.message || String(error)}`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return { errors };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Validates SubAgent structure recursively
|
|
324
|
+
*/
|
|
325
|
+
protected validateSubAgents(
|
|
326
|
+
subAgents: AgentSpec['SubAgents']
|
|
327
|
+
): { errors: string[]; corrected: boolean } {
|
|
328
|
+
const errors: string[] = [];
|
|
329
|
+
let corrected = false;
|
|
330
|
+
|
|
331
|
+
if (!subAgents || subAgents.length === 0) {
|
|
332
|
+
return { errors, corrected };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
for (let i = 0; i < subAgents.length; i++) {
|
|
336
|
+
const subAgent = subAgents[i];
|
|
337
|
+
|
|
338
|
+
// Validate Type field
|
|
339
|
+
if (!subAgent.Type) {
|
|
340
|
+
errors.push(`❌ SubAgent at index ${i} is missing required "Type" field (must be "child" or "related")`);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (subAgent.Type !== 'child' && subAgent.Type !== 'related') {
|
|
345
|
+
errors.push(`❌ SubAgent at index ${i} has invalid Type "${subAgent.Type}" (must be "child" or "related")`);
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Validate SubAgent.SubAgent field
|
|
350
|
+
if (!subAgent.SubAgent) {
|
|
351
|
+
errors.push(`❌ SubAgent at index ${i} is missing required "SubAgent" field`);
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// SubAgent.ID can be empty string "" for NEW sub-agents (Builder creates them recursively)
|
|
356
|
+
// Only validate that ID is not null/undefined
|
|
357
|
+
if (subAgent.SubAgent.ID === null || subAgent.SubAgent.ID === undefined) {
|
|
358
|
+
errors.push(`❌ SubAgent at index ${i} has null/undefined SubAgent.ID (use empty string "" for new sub-agents, or provide existing agent GUID)`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Validate SubAgent.SubAgent.Name
|
|
362
|
+
if (!subAgent.SubAgent.Name || subAgent.SubAgent.Name.trim().length === 0) {
|
|
363
|
+
errors.push(`❌ SubAgent at index ${i} is missing SubAgent.Name`);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Validate TypeID for sub-agent
|
|
367
|
+
if (!subAgent.SubAgent.TypeID) {
|
|
368
|
+
errors.push(`❌ SubAgent[${i}] "${subAgent.SubAgent.Name}" is missing TypeID field`);
|
|
369
|
+
} else {
|
|
370
|
+
// Validate Loop/Flow specific requirements for sub-agents
|
|
371
|
+
const isLoopSubAgent = subAgent.SubAgent.TypeID.includes('Loop');
|
|
372
|
+
const isFlowSubAgent = subAgent.SubAgent.TypeID.includes('Flow');
|
|
373
|
+
|
|
374
|
+
if (isLoopSubAgent) {
|
|
375
|
+
// Loop sub-agents require at least one prompt
|
|
376
|
+
if (!subAgent.SubAgent.Prompts || subAgent.SubAgent.Prompts.length === 0) {
|
|
377
|
+
errors.push(`❌ Loop SubAgent[${i}] "${subAgent.SubAgent.Name}" requires at least ONE prompt in Prompts array`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (isFlowSubAgent) {
|
|
382
|
+
// Flow sub-agents MUST have empty Prompts array
|
|
383
|
+
if (subAgent.SubAgent.Prompts && subAgent.SubAgent.Prompts.length > 0) {
|
|
384
|
+
errors.push(`❌ Flow SubAgent[${i}] "${subAgent.SubAgent.Name}" MUST have empty Prompts array. Use Prompt steps or Loop sub-agents for LLM functionality.`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Flow sub-agents require Steps
|
|
388
|
+
if (!subAgent.SubAgent.Steps || subAgent.SubAgent.Steps.length === 0) {
|
|
389
|
+
errors.push(`❌ Flow SubAgent[${i}] "${subAgent.SubAgent.Name}" requires at least ONE step in Steps array`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Auto-correct: Add default StartingPayloadValidationMode if missing
|
|
395
|
+
if (!subAgent.SubAgent.StartingPayloadValidationMode) {
|
|
396
|
+
subAgent.SubAgent.StartingPayloadValidationMode = 'Fail';
|
|
397
|
+
corrected = true;
|
|
398
|
+
console.log(`✓ Auto-corrected: Added SubAgent[${i}].SubAgent.StartingPayloadValidationMode = "Fail"`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// Auto-correct: Add default Status if missing
|
|
402
|
+
if (!subAgent.SubAgent.Status) {
|
|
403
|
+
subAgent.SubAgent.Status = 'Active';
|
|
404
|
+
corrected = true;
|
|
405
|
+
console.log(`✓ Auto-corrected: Added SubAgent[${i}].SubAgent.Status = "Active"`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Validate related-specific fields
|
|
409
|
+
if (subAgent.Type === 'related' && !subAgent.AgentRelationshipID) {
|
|
410
|
+
// AgentRelationshipID can be empty for new relationships, it will be created on save
|
|
411
|
+
// This is not an error
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Recursively validate nested sub-agents
|
|
415
|
+
if (subAgent.SubAgent.SubAgents && subAgent.SubAgent.SubAgents.length > 0) {
|
|
416
|
+
const nestedValidation = this.validateSubAgents(subAgent.SubAgent.SubAgents);
|
|
417
|
+
if (nestedValidation.errors.length > 0) {
|
|
418
|
+
// Prefix nested errors with parent context
|
|
419
|
+
const prefixedErrors = nestedValidation.errors.map(
|
|
420
|
+
err => `SubAgent[${i}] "${subAgent.SubAgent.Name}" -> ${err}`
|
|
421
|
+
);
|
|
422
|
+
errors.push(...prefixedErrors);
|
|
423
|
+
}
|
|
424
|
+
if (nestedValidation.corrected) {
|
|
425
|
+
corrected = true;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return { errors, corrected };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Formats validation errors into a clear, actionable message
|
|
435
|
+
*/
|
|
436
|
+
protected formatValidationErrors(errors: string[]): string {
|
|
437
|
+
if (errors.length === 0) {
|
|
438
|
+
return '';
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const header = '## AgentSpec Validation Errors\n\nThe generated AgentSpec has the following issues that must be fixed:\n\n';
|
|
442
|
+
const errorList = errors.map((err, idx) => `${idx + 1}. ${err}`).join('\n');
|
|
443
|
+
const footer = '\n\n**Please fix these issues and regenerate the AgentSpec.**';
|
|
444
|
+
|
|
445
|
+
return header + errorList + footer;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { BaseAgent } from '@memberjunction/ai-agents';
|
|
2
|
+
import { ExecuteAgentParams, AgentConfiguration, BaseAgentNextStep, AgentSpec } from '@memberjunction/ai-core-plus';
|
|
3
|
+
import { RegisterClass } from '@memberjunction/global';
|
|
4
|
+
import { Metadata } from '@memberjunction/core';
|
|
5
|
+
import { AIAgentRunStepEntityExtended } from '@memberjunction/core-entities';
|
|
6
|
+
import { AgentSpecSync } from '../agent-spec-sync';
|
|
7
|
+
import { TemplateEngineServer } from '@memberjunction/templates';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Builder Agent - Persists validated AgentSpec to database via AgentSpecSync
|
|
11
|
+
*
|
|
12
|
+
* This is a code-based agent that overrides executeAgentInternal to bypass
|
|
13
|
+
* the normal chat loop and directly execute persistence logic. It receives
|
|
14
|
+
* a validated AgentSpec from the Architect Agent and saves it to the database
|
|
15
|
+
* using AgentSpecSync.
|
|
16
|
+
*
|
|
17
|
+
* Key responsibilities:
|
|
18
|
+
* - Receive validated AgentSpec from payload
|
|
19
|
+
* - Create AgentSpecSync instance
|
|
20
|
+
* - Persist to database
|
|
21
|
+
* - Track mutations in AI Agent Run Steps table
|
|
22
|
+
* - Return success with created agent ID, or failure with error details
|
|
23
|
+
*
|
|
24
|
+
* This agent does NOT iterate - it runs once and returns Success or Failed.
|
|
25
|
+
*/
|
|
26
|
+
@RegisterClass(BaseAgent, 'AgentBuilderAgent')
|
|
27
|
+
export class AgentBuilderAgent extends BaseAgent {
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Override executeAgentInternal to run code instead of chat loop.
|
|
31
|
+
*
|
|
32
|
+
* Directly creates the AI Agent Run Step with mutations from AgentSpecSync.
|
|
33
|
+
*/
|
|
34
|
+
protected override async executeAgentInternal<P = any>(
|
|
35
|
+
params: ExecuteAgentParams<P>,
|
|
36
|
+
_config: AgentConfiguration
|
|
37
|
+
): Promise<{ finalStep: BaseAgentNextStep<P>; stepCount: number }> {
|
|
38
|
+
|
|
39
|
+
console.log('🔨 Builder Agent: Starting agent persistence...');
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// Validate payload
|
|
43
|
+
const agentSpec = params.payload as AgentSpec;
|
|
44
|
+
if (!agentSpec) {
|
|
45
|
+
throw new Error('No AgentSpec found in payload - ensure Architect Agent provided valid AgentSpec');
|
|
46
|
+
}
|
|
47
|
+
if (!agentSpec.Name) {
|
|
48
|
+
throw new Error('AgentSpec is missing required Name field');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.log(`🔨 Builder Agent: Creating agent "${agentSpec.Name}"...`);
|
|
52
|
+
|
|
53
|
+
// Create AgentSpecSync and save to database
|
|
54
|
+
const specSync = new AgentSpecSync(agentSpec, params.contextUser);
|
|
55
|
+
specSync.markDirty();
|
|
56
|
+
|
|
57
|
+
const result = await specSync.SaveToDatabase();
|
|
58
|
+
|
|
59
|
+
if (!result.success) {
|
|
60
|
+
throw new Error('AgentSpecSync.SaveToDatabase() returned success=false');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log(`✅ Builder Agent: Successfully created agent with ID: ${result.agentId}`);
|
|
64
|
+
|
|
65
|
+
// Refresh metadata and template caches
|
|
66
|
+
console.log('🔄 Builder Agent: Refreshing metadata and template caches...');
|
|
67
|
+
const md = new Metadata();
|
|
68
|
+
await md.Refresh();
|
|
69
|
+
|
|
70
|
+
const templateEngine = new TemplateEngineServer();
|
|
71
|
+
await templateEngine.Config(true, params.contextUser);
|
|
72
|
+
console.log('✅ Builder Agent: Metadata and template caches refreshed');
|
|
73
|
+
|
|
74
|
+
// Create AI Agent Run Step record directly
|
|
75
|
+
const agentRunId = params.parentRun?.ID || 'unknown';
|
|
76
|
+
const stepEntity = await md.GetEntityObject<AIAgentRunStepEntityExtended>(
|
|
77
|
+
'MJ: AI Agent Run Steps',
|
|
78
|
+
params.contextUser
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
stepEntity.AgentRunID = agentRunId;
|
|
82
|
+
stepEntity.StepNumber = 2; // Validation is step 1, this is step 2
|
|
83
|
+
stepEntity.StepType = 'Decision';
|
|
84
|
+
stepEntity.StepName = 'Sync Agent Spec';
|
|
85
|
+
stepEntity.Status = 'Completed';
|
|
86
|
+
stepEntity.Success = true;
|
|
87
|
+
stepEntity.StartedAt = new Date();
|
|
88
|
+
stepEntity.CompletedAt = new Date();
|
|
89
|
+
|
|
90
|
+
// InputData = Full AgentSpec
|
|
91
|
+
stepEntity.InputData = JSON.stringify(agentSpec, null, 2);
|
|
92
|
+
stepEntity.PayloadAtStart = JSON.stringify(params.payload);
|
|
93
|
+
|
|
94
|
+
// OutputData = Mutations array
|
|
95
|
+
stepEntity.OutputData = JSON.stringify(result.mutations, null, 2);
|
|
96
|
+
stepEntity.PayloadAtEnd = stepEntity.PayloadAtStart;
|
|
97
|
+
|
|
98
|
+
await stepEntity.Save();
|
|
99
|
+
console.log(`✅ Builder Agent: Created AI Agent Run Step with ID: ${stepEntity.ID}`);
|
|
100
|
+
|
|
101
|
+
// Return success
|
|
102
|
+
const updatedSpec = { ...agentSpec, ID: result.agentId };
|
|
103
|
+
return {
|
|
104
|
+
finalStep: {
|
|
105
|
+
terminate: true,
|
|
106
|
+
step: 'Success',
|
|
107
|
+
reasoning: `Successfully created agent "${agentSpec.Name}" with ID: ${result.agentId}`,
|
|
108
|
+
newPayload: updatedSpec as P
|
|
109
|
+
},
|
|
110
|
+
stepCount: 2
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
} catch (error: any) {
|
|
114
|
+
console.error('❌ Builder Agent: Failed to create agent:', error);
|
|
115
|
+
|
|
116
|
+
// Create failed AI Agent Run Step
|
|
117
|
+
const agentRunId = params.parentRun?.ID || 'unknown';
|
|
118
|
+
const md = new Metadata();
|
|
119
|
+
const stepEntity = await md.GetEntityObject<AIAgentRunStepEntityExtended>(
|
|
120
|
+
'MJ: AI Agent Run Steps',
|
|
121
|
+
params.contextUser
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
stepEntity.AgentRunID = agentRunId;
|
|
125
|
+
stepEntity.StepNumber = 2;
|
|
126
|
+
stepEntity.StepType = 'Decision';
|
|
127
|
+
stepEntity.StepName = 'Sync Agent Spec';
|
|
128
|
+
stepEntity.Status = 'Failed';
|
|
129
|
+
stepEntity.Success = false;
|
|
130
|
+
stepEntity.StartedAt = new Date();
|
|
131
|
+
stepEntity.CompletedAt = new Date();
|
|
132
|
+
stepEntity.ErrorMessage = error?.message || String(error);
|
|
133
|
+
|
|
134
|
+
stepEntity.InputData = JSON.stringify(params.payload, null, 2);
|
|
135
|
+
stepEntity.PayloadAtStart = JSON.stringify(params.payload);
|
|
136
|
+
stepEntity.OutputData = JSON.stringify({ error: error?.message || String(error) });
|
|
137
|
+
stepEntity.PayloadAtEnd = stepEntity.PayloadAtStart;
|
|
138
|
+
|
|
139
|
+
await stepEntity.Save();
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
finalStep: {
|
|
143
|
+
terminate: true,
|
|
144
|
+
step: 'Failed',
|
|
145
|
+
reasoning: `Failed to create agent: ${error?.message || String(error)}`
|
|
146
|
+
},
|
|
147
|
+
stepCount: 2
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @memberjunction/agent-manager-core
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Core interfaces and types for the MemberJunction Agent Manager system
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
// Export all interfaces
|
|
8
8
|
export * from './old/agent-definition.interface';
|
|
9
9
|
|
|
10
|
-
export * from './agent-spec-sync';
|
|
10
|
+
export * from './agent-spec-sync';
|
|
11
|
+
|
|
12
|
+
// Export agent implementations
|
|
13
|
+
export * from './agents/architect-agent';
|
|
14
|
+
export * from './agents/builder-agent';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Loads the Agent Manager Core module and ensures all agent classes are registered.
|
|
18
|
+
* This function prevents tree shaking from removing the agent class registrations.
|
|
19
|
+
*
|
|
20
|
+
* The function itself doesn't need to do anything - importing the module is enough
|
|
21
|
+
* to trigger the @RegisterClass decorators on AgentArchitectAgent and AgentBuilderAgent.
|
|
22
|
+
*/
|
|
23
|
+
export function LoadAgentManagerCore() {
|
|
24
|
+
// Forces module to load and decorators to execute
|
|
25
|
+
// Classes are already exported and decorators run on import
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Auto-load on import to ensure decorators execute
|
|
29
|
+
LoadAgentManagerCore();
|