@memberjunction/ai-agent-manager 2.109.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.
@@ -51,9 +51,8 @@ export class AgentArchitectAgent extends BaseAgent {
51
51
  // Apply payload changes to get the spec that would be sent forward
52
52
  const payloadWithChanges = this.applyPayloadChanges<P>(currentPayload, nextStep);
53
53
 
54
- // Extract the agentSpec from the nested payload structure
55
- // The payload has structure: { metadata, requirements, design, agentSpec }
56
- const agentSpec = (payloadWithChanges as any).agentSpec as AgentSpec;
54
+ // The payload IS the AgentSpec
55
+ const agentSpec = payloadWithChanges as AgentSpec;
57
56
 
58
57
  console.log('🏗️ Architect Agent: Validating AgentSpec:', agentSpec ? `Name="${agentSpec.Name}"` : 'agentSpec is null/undefined');
59
58
 
@@ -74,13 +73,9 @@ export class AgentArchitectAgent extends BaseAgent {
74
73
  // If spec was auto-corrected, update the payload with corrected spec
75
74
  if (validation.correctedSpec) {
76
75
  console.log('✓ Architect Agent: Auto-corrected AgentSpec, updating payload');
77
- const updatedPayload = {
78
- ...(payloadWithChanges as any),
79
- agentSpec: validation.correctedSpec
80
- };
81
76
  return {
82
77
  ...nextStep,
83
- newPayload: updatedPayload as P
78
+ newPayload: validation.correctedSpec as P
84
79
  };
85
80
  }
86
81
 
@@ -140,7 +135,116 @@ export class AgentArchitectAgent extends BaseAgent {
140
135
  }
141
136
  }
142
137
 
143
- // 4. Validate optional but important fields
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
144
248
  if (correctedSpec.FinalPayloadValidationMode &&
145
249
  !['Fail', 'Retry', 'Warn'].includes(correctedSpec.FinalPayloadValidationMode)) {
146
250
  errors.push(`❌ FinalPayloadValidationMode must be "Fail", "Retry", or "Warn", got: "${correctedSpec.FinalPayloadValidationMode}"`);
@@ -216,7 +320,7 @@ export class AgentArchitectAgent extends BaseAgent {
216
320
  }
217
321
 
218
322
  /**
219
- * Validates SubAgent structure
323
+ * Validates SubAgent structure recursively
220
324
  */
221
325
  protected validateSubAgents(
222
326
  subAgents: AgentSpec['SubAgents']
@@ -259,6 +363,34 @@ export class AgentArchitectAgent extends BaseAgent {
259
363
  errors.push(`❌ SubAgent at index ${i} is missing SubAgent.Name`);
260
364
  }
261
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
+
262
394
  // Auto-correct: Add default StartingPayloadValidationMode if missing
263
395
  if (!subAgent.SubAgent.StartingPayloadValidationMode) {
264
396
  subAgent.SubAgent.StartingPayloadValidationMode = 'Fail';
@@ -266,11 +398,33 @@ export class AgentArchitectAgent extends BaseAgent {
266
398
  console.log(`✓ Auto-corrected: Added SubAgent[${i}].SubAgent.StartingPayloadValidationMode = "Fail"`);
267
399
  }
268
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
+
269
408
  // Validate related-specific fields
270
409
  if (subAgent.Type === 'related' && !subAgent.AgentRelationshipID) {
271
410
  // AgentRelationshipID can be empty for new relationships, it will be created on save
272
411
  // This is not an error
273
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
+ }
274
428
  }
275
429
 
276
430
  return { errors, corrected };
@@ -1,7 +1,10 @@
1
1
  import { BaseAgent } from '@memberjunction/ai-agents';
2
2
  import { ExecuteAgentParams, AgentConfiguration, BaseAgentNextStep, AgentSpec } from '@memberjunction/ai-core-plus';
3
3
  import { RegisterClass } from '@memberjunction/global';
4
+ import { Metadata } from '@memberjunction/core';
5
+ import { AIAgentRunStepEntityExtended } from '@memberjunction/core-entities';
4
6
  import { AgentSpecSync } from '../agent-spec-sync';
7
+ import { TemplateEngineServer } from '@memberjunction/templates';
5
8
 
6
9
  /**
7
10
  * Builder Agent - Persists validated AgentSpec to database via AgentSpecSync
@@ -15,6 +18,7 @@ import { AgentSpecSync } from '../agent-spec-sync';
15
18
  * - Receive validated AgentSpec from payload
16
19
  * - Create AgentSpecSync instance
17
20
  * - Persist to database
21
+ * - Track mutations in AI Agent Run Steps table
18
22
  * - Return success with created agent ID, or failure with error details
19
23
  *
20
24
  * This agent does NOT iterate - it runs once and returns Success or Failed.
@@ -23,8 +27,9 @@ import { AgentSpecSync } from '../agent-spec-sync';
23
27
  export class AgentBuilderAgent extends BaseAgent {
24
28
 
25
29
  /**
26
- * Override executeAgentInternal to run code instead of chat loop
27
- * This follows the Skip Code Generator pattern
30
+ * Override executeAgentInternal to run code instead of chat loop.
31
+ *
32
+ * Directly creates the AI Agent Run Step with mutations from AgentSpecSync.
28
33
  */
29
34
  protected override async executeAgentInternal<P = any>(
30
35
  params: ExecuteAgentParams<P>,
@@ -34,55 +39,112 @@ export class AgentBuilderAgent extends BaseAgent {
34
39
  console.log('🔨 Builder Agent: Starting agent persistence...');
35
40
 
36
41
  try {
37
- // Extract AgentSpec from nested payload structure
38
- // The payload has structure: { metadata, requirements, design, agentSpec }
39
- const agentSpec = (params.payload as any).agentSpec as AgentSpec;
40
-
42
+ // Validate payload
43
+ const agentSpec = params.payload as AgentSpec;
41
44
  if (!agentSpec) {
42
- throw new Error('No AgentSpec found in payload.agentSpec - ensure Architect Agent placed it there');
45
+ throw new Error('No AgentSpec found in payload - ensure Architect Agent provided valid AgentSpec');
43
46
  }
44
-
45
47
  if (!agentSpec.Name) {
46
48
  throw new Error('AgentSpec is missing required Name field');
47
49
  }
48
50
 
49
51
  console.log(`🔨 Builder Agent: Creating agent "${agentSpec.Name}"...`);
50
52
 
51
- // Create AgentSpecSync instance
53
+ // Create AgentSpecSync and save to database
52
54
  const specSync = new AgentSpecSync(agentSpec, params.contextUser);
53
-
54
- // Mark as dirty to ensure it saves
55
55
  specSync.markDirty();
56
56
 
57
- // Save to database
58
- const agentId = await specSync.SaveToDatabase();
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');
59
73
 
60
- console.log(`✅ Builder Agent: Successfully created agent with ID: ${agentId}`);
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
+ );
61
80
 
62
- // Return success with the created agent ID
63
- const updatedSpec = { ...agentSpec, ID: agentId };
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();
64
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 };
65
103
  return {
66
104
  finalStep: {
67
105
  terminate: true,
68
106
  step: 'Success',
69
- reasoning: `Successfully created agent "${agentSpec.Name}" with ID: ${agentId}`,
107
+ reasoning: `Successfully created agent "${agentSpec.Name}" with ID: ${result.agentId}`,
70
108
  newPayload: updatedSpec as P
71
109
  },
72
- stepCount: 1
110
+ stepCount: 2
73
111
  };
74
112
 
75
113
  } catch (error: any) {
76
114
  console.error('❌ Builder Agent: Failed to create agent:', error);
77
115
 
78
- // Return failure with error details
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
+
79
141
  return {
80
142
  finalStep: {
81
143
  terminate: true,
82
144
  step: 'Failed',
83
145
  reasoning: `Failed to create agent: ${error?.message || String(error)}`
84
146
  },
85
- stepCount: 1
147
+ stepCount: 2
86
148
  };
87
149
  }
88
150
  }
@@ -182,4 +182,63 @@ export interface AgentManagerPayload {
182
182
  issues: string[];
183
183
  }[];
184
184
  };
185
+
186
+ /**
187
+ * Agent Loader section - for modification workflow
188
+ * Contains the loaded agent when modifying an existing agent
189
+ */
190
+ loadedAgent?: {
191
+ /** Full AgentSpec loaded from database with all nested structure */
192
+ agentSpec: any; // AgentSpec type from CorePlus
193
+ /** Copy of original spec for comparison during modification */
194
+ originalSpec: any; // AgentSpec type from CorePlus
195
+ /** When the agent was last modified in database */
196
+ lastModified: string;
197
+ /** Agent's current status */
198
+ status: string;
199
+ };
200
+
201
+ /**
202
+ * Modification Planner section - for modification workflow
203
+ * Contains the detailed plan for modifying an existing agent
204
+ */
205
+ modificationPlan?: {
206
+ /** High-level summary of all changes to be made */
207
+ changeSummary: string;
208
+ /** Fields that will be updated (field name -> old/new values) */
209
+ fieldsToUpdate?: Record<string, { old: any; new: any }>;
210
+ /** Actions to add (with action IDs from Find Best Action) */
211
+ actionsToAdd?: Array<{ id: string; name: string; reason: string }>;
212
+ /** Action IDs to remove from the agent */
213
+ actionsToRemove?: string[];
214
+ /** Sub-agents modifications (add/remove/update) */
215
+ subAgentsToModify?: Array<{
216
+ operation: 'add' | 'remove' | 'update';
217
+ subAgentName?: string;
218
+ subAgentId?: string;
219
+ details?: any;
220
+ }>;
221
+ /** Prompts to update or add */
222
+ promptsToUpdate?: Array<{
223
+ operation: 'add' | 'update' | 'remove';
224
+ agentName: string;
225
+ promptText?: string;
226
+ promptRole?: string;
227
+ promptPosition?: string;
228
+ }>;
229
+ /** Steps modifications for Flow agents only */
230
+ stepsToModify?: Array<{
231
+ operation: 'add' | 'remove' | 'update';
232
+ stepName?: string;
233
+ stepType?: string;
234
+ details?: any;
235
+ }>;
236
+ /** Paths modifications for Flow agents only */
237
+ pathsToModify?: Array<{
238
+ operation: 'add' | 'remove' | 'update';
239
+ from?: string;
240
+ to?: string;
241
+ details?: any;
242
+ }>;
243
+ };
185
244
  }