@memberjunction/ai-agent-manager 2.112.0 → 2.113.1

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.
@@ -1,7 +1,7 @@
1
1
  import { BaseAgent, PayloadManager } from '@memberjunction/ai-agents';
2
2
  import { ExecuteAgentParams, BaseAgentNextStep, AgentSpec } from '@memberjunction/ai-core-plus';
3
3
  import { AIAgentRunEntityExtended, AIAgentRunStepEntityExtended, ActionEntity, AIPromptEntity } from '@memberjunction/core-entities';
4
- import { Metadata, RunView } from '@memberjunction/global';
4
+ import { Metadata, RunView } from '@memberjunction/core';
5
5
  import { RegisterClass } from '@memberjunction/global';
6
6
 
7
7
  /**
@@ -22,442 +22,435 @@ import { RegisterClass } from '@memberjunction/global';
22
22
  */
23
23
  @RegisterClass(BaseAgent, 'AgentArchitectAgent')
24
24
  export class AgentArchitectAgent extends BaseAgent {
25
- /**
26
- * Applies payload changes if any are requested in the next step
27
- */
28
- protected applyPayloadChanges<P>(payload: P, nextStep: BaseAgentNextStep<P>): P {
29
- if (nextStep.payloadChangeRequest) {
30
- const pm = new PayloadManager();
31
- const result = pm.applyAgentChangeRequest<P>(payload, nextStep.payloadChangeRequest);
32
- return result.result || payload;
33
- }
34
- return payload;
35
- }
36
-
37
- protected override async validateSuccessNextStep<P>(
38
- params: ExecuteAgentParams,
39
- nextStep: BaseAgentNextStep<P>,
40
- currentPayload: P,
41
- agentRun: AIAgentRunEntityExtended,
42
- currentStep: AIAgentRunStepEntityExtended
43
- ): Promise<BaseAgentNextStep<P>> {
44
- // First call base validation
45
- const baseValidation = await super.validateSuccessNextStep(params, nextStep, currentPayload, agentRun, currentStep);
46
- if (baseValidation.step === 'Retry') {
47
- return baseValidation;
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;
48
36
  }
49
37
 
50
- // Apply payload changes to get the spec that would be sent forward
51
- const payloadWithChanges = this.applyPayloadChanges<P>(currentPayload, nextStep);
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
+ }
52
50
 
53
- // The payload IS the AgentSpec
54
- const agentSpec = payloadWithChanges as AgentSpec;
51
+ // Apply payload changes to get the spec that would be sent forward
52
+ const payloadWithChanges = this.applyPayloadChanges<P>(currentPayload, nextStep);
55
53
 
56
- console.log('🏗️ Architect Agent: Validating AgentSpec:', agentSpec ? `Name="${agentSpec.Name}"` : 'agentSpec is null/undefined');
54
+ // The payload IS the AgentSpec
55
+ const agentSpec = payloadWithChanges as AgentSpec;
57
56
 
58
- // Validate the AgentSpec
59
- const validation = await this.validateAgentSpec(agentSpec, params);
57
+ console.log('🏗️ Architect Agent: Validating AgentSpec:', agentSpec ? `Name="${agentSpec.Name}"` : 'agentSpec is null/undefined');
60
58
 
61
- if (validation.errors.length > 0) {
62
- console.log('❌ Architect Agent: Validation failed with errors:', validation.errors);
63
- // Return retry with detailed error messages
64
- return {
65
- ...nextStep,
66
- step: 'Retry',
67
- retryInstructions: this.formatValidationErrors(validation.errors),
68
- newPayload: nextStep.newPayload,
69
- };
70
- }
59
+ // Validate the AgentSpec
60
+ const validation = await this.validateAgentSpec(agentSpec, params);
71
61
 
72
- // If spec was auto-corrected, update the payload with corrected spec
73
- if (validation.correctedSpec) {
74
- console.log('✓ Architect Agent: Auto-corrected AgentSpec, updating payload');
75
- return {
76
- ...nextStep,
77
- newPayload: validation.correctedSpec as P,
78
- };
79
- }
80
-
81
- console.log('✅ Architect Agent: AgentSpec validation passed');
82
- return nextStep;
83
- }
84
-
85
- /**
86
- * Validates an AgentSpec object
87
- * Returns errors array and potentially a corrected spec
88
- */
89
- protected async validateAgentSpec(spec: AgentSpec, params: ExecuteAgentParams): Promise<{ errors: string[]; correctedSpec?: AgentSpec }> {
90
- const errors: string[] = [];
91
- let corrected = false;
92
-
93
- // Check if spec exists
94
- if (!spec) {
95
- errors.push(
96
- '❌ AgentSpec is null or undefined. Ensure you place the AgentSpec in payload.agentSpec (inside a payloadChangeRequest with newElements or replaceElements)'
97
- );
98
- return { errors };
99
- }
100
-
101
- const correctedSpec = { ...spec };
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
+ }
102
72
 
103
- // 1. Validate required fields
104
- if (!correctedSpec.Name || correctedSpec.Name.trim().length === 0) {
105
- errors.push('❌ AgentSpec.Name is required and cannot be empty');
106
- }
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
+ }
107
81
 
108
- if (!correctedSpec.StartingPayloadValidationMode) {
109
- // Auto-correct: add default
110
- correctedSpec.StartingPayloadValidationMode = 'Fail';
111
- corrected = true;
112
- console.log('✓ Auto-corrected: Added default StartingPayloadValidationMode = "Fail"');
113
- } else if (correctedSpec.StartingPayloadValidationMode !== 'Fail' && correctedSpec.StartingPayloadValidationMode !== 'Warn') {
114
- errors.push(
115
- `❌ StartingPayloadValidationMode must be either "Fail" or "Warn", got: "${correctedSpec.StartingPayloadValidationMode}"`
116
- );
82
+ console.log('✅ Architect Agent: AgentSpec validation passed');
83
+ return nextStep;
117
84
  }
118
85
 
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
- }
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
+ }
126
102
 
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
- }
103
+ const correctedSpec = { ...spec };
137
104
 
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
- }
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
+ }
143
109
 
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
- }
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
+ }
152
118
 
153
- // 5. Validate agent type-specific requirements
154
- const isLoopAgent = specWithType.TypeID?.includes('Loop');
155
- const isFlowAgent = specWithType.TypeID?.includes('Flow');
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
+ }
156
126
 
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
- }
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
+ }
163
137
 
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(
168
- '❌ 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.'
169
- );
170
- }
171
-
172
- // Flow agents require Steps and Paths
173
- if (!correctedSpec.Steps || correctedSpec.Steps.length === 0) {
174
- errors.push('❌ Flow agents require at least ONE step in the Steps array');
175
- }
176
- // Paths are technically optional (single-step flows), but validate if present
177
- if (correctedSpec.Steps && correctedSpec.Steps.length > 1) {
178
- if (!correctedSpec.Paths || correctedSpec.Paths.length === 0) {
179
- errors.push('❌ Flow agents with multiple steps require at least ONE path in the Paths array');
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"');
180
142
  }
181
- }
182
143
 
183
- // Validate that at least one step is marked as StartingStep
184
- if (correctedSpec.Steps && correctedSpec.Steps.length > 0) {
185
- const hasStartingStep = correctedSpec.Steps.some((step) => step.StartingStep === true);
186
- if (!hasStartingStep) {
187
- errors.push('❌ Flow agents require at least ONE step with StartingStep: true');
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}"`);
188
151
  }
189
152
 
190
- // Validate each step based on its type
191
- for (let i = 0; i < correctedSpec.Steps.length; i++) {
192
- const step = correctedSpec.Steps[i];
153
+ // 5. Validate agent type-specific requirements
154
+ const isLoopAgent = specWithType.TypeID?.includes('Loop');
155
+ const isFlowAgent = specWithType.TypeID?.includes('Flow');
193
156
 
194
- // Validate Action steps
195
- if (step.StepType === 'Action') {
196
- if (!step.ActionID) {
197
- errors.push(`❌ Action step "${step.Name}" (index ${i}) must have ActionID field`);
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');
198
161
  }
162
+ }
199
163
 
200
- // Validate ActionInputMapping if provided (supports both string and object)
201
- if (step.ActionInputMapping) {
202
- try {
203
- if (typeof step.ActionInputMapping === 'string') {
204
- JSON.parse(step.ActionInputMapping);
205
- }
206
- // else it's already an object, which is valid and will be stringified later
207
- } catch (e: any) {
208
- errors.push(`❌ Step "${step.Name}" (index ${i}) has invalid ActionInputMapping JSON: ${e?.message || String(e)}`);
209
- }
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.');
210
168
  }
211
169
 
212
- // Validate ActionOutputMapping if provided (supports both string and object)
213
- if (step.ActionOutputMapping) {
214
- try {
215
- if (typeof step.ActionOutputMapping === 'string') {
216
- JSON.parse(step.ActionOutputMapping);
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');
217
178
  }
218
- // else it's already an object, which is valid and will be stringified later
219
- } catch (e: any) {
220
- errors.push(`❌ Step "${step.Name}" (index ${i}) has invalid ActionOutputMapping JSON: ${e?.message || String(e)}`);
221
- }
222
179
  }
223
- }
224
-
225
- // Validate Prompt steps
226
- if (step.StepType === 'Prompt') {
227
- // If PromptID is empty or not provided, PromptText is required for inline creation
228
- if (!step.PromptID || step.PromptID === '') {
229
- if (!step.PromptText || step.PromptText.trim() === '') {
230
- errors.push(
231
- `❌ Prompt step "${step.Name}" (index ${i}) has empty PromptID but missing PromptText. For inline prompt creation, PromptText is required.`
232
- );
233
- }
234
- // PromptName and PromptDescription are recommended but not required
235
- if (!step.PromptName) {
236
- console.log(
237
- `⚠️ Warning: Prompt step "${step.Name}" (index ${i}) is missing PromptName (will default to "[Step Name] Prompt")`
238
- );
239
- }
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
+ }
240
244
  }
241
- // If PromptID is provided (existing prompt), other fields are optional
242
- }
243
-
244
- // Validate Sub-Agent steps
245
- if (step.StepType === 'Sub-Agent') {
246
- // SubAgentID can be empty "" for new sub-agents (will be linked by name matching)
247
- // No validation needed here - linking happens in AgentSpecSync
248
- }
249
245
  }
250
- }
251
- }
252
246
 
253
- // 6. Validate optional but important fields
254
- if (correctedSpec.FinalPayloadValidationMode && !['Fail', 'Retry', 'Warn'].includes(correctedSpec.FinalPayloadValidationMode)) {
255
- errors.push(`❌ FinalPayloadValidationMode must be "Fail", "Retry", or "Warn", got: "${correctedSpec.FinalPayloadValidationMode}"`);
256
- }
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
257
 
258
- if (correctedSpec.InvocationMode && !['Any', 'Agent', 'User', 'Never'].includes(correctedSpec.InvocationMode)) {
259
- errors.push(`❌ InvocationMode must be "Any", "Agent", "User", or "Never", got: "${correctedSpec.InvocationMode}"`);
258
+ return {
259
+ errors,
260
+ correctedSpec: corrected ? correctedSpec : undefined
261
+ };
260
262
  }
261
263
 
262
- return {
263
- errors,
264
- correctedSpec: corrected ? correctedSpec : undefined,
265
- };
266
- }
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
+ }
267
276
 
268
- /**
269
- * Validates that action IDs exist in the database
270
- */
271
- protected async validateActions(actions: AgentSpec['Actions'], params: ExecuteAgentParams): Promise<{ errors: string[] }> {
272
- const errors: string[] = [];
277
+ try {
278
+ const rv = new RunView();
279
+ const actionIds = actions
280
+ .map(a => a.ActionID)
281
+ .filter(id => id && id.trim().length > 0);
273
282
 
274
- if (!actions || actions.length === 0) {
275
- return { errors };
276
- }
283
+ if (actionIds.length === 0) {
284
+ errors.push('❌ All actions must have an ActionID');
285
+ return { errors };
286
+ }
277
287
 
278
- try {
279
- const rv = new RunView();
280
- const actionIds = actions.map((a) => a.ActionID).filter((id) => id && id.trim().length > 0);
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
+ }
281
300
 
282
- if (actionIds.length === 0) {
283
- errors.push('❌ All actions must have an ActionID');
284
- return { errors };
285
- }
286
-
287
- // Query database for these action IDs
288
- const filter = actionIds.map((id) => `ID='${id}'`).join(' OR ');
289
- const result = await rv.RunView<ActionEntity>(
290
- {
291
- EntityName: 'Actions',
292
- ExtraFilter: filter,
293
- ResultType: 'entity_object',
294
- },
295
- params.contextUser
296
- );
297
-
298
- if (!result.Success) {
299
- errors.push(`❌ Failed to validate actions: ${result.ErrorMessage}`);
300
- return { errors };
301
- }
301
+ const foundIds = new Set(result.Results.map(a => a.ID));
302
302
 
303
- const foundIds = new Set(result.Results.map((a) => a.ID));
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
+ }
304
309
 
305
- // Check which actions weren't found
306
- for (const action of actions) {
307
- if (!action.ActionID) {
308
- errors.push('❌ Action is missing ActionID field');
309
- continue;
310
- }
310
+ if (!foundIds.has(action.ActionID)) {
311
+ errors.push(`❌ Action with ID "${action.ActionID}" not found in database. Please use "Find Candidate Actions" or "List Actions" to get valid action IDs.`);
312
+ }
313
+ }
311
314
 
312
- if (!foundIds.has(action.ActionID)) {
313
- errors.push(
314
- `❌ Action with ID "${action.ActionID}" not found in database. Please use "Find Candidate Actions" or "List Actions" to get valid action IDs.`
315
- );
315
+ } catch (error: any) {
316
+ errors.push(`❌ Error validating actions: ${error?.message || String(error)}`);
316
317
  }
317
- }
318
- } catch (error: any) {
319
- errors.push(`❌ Error validating actions: ${error?.message || String(error)}`);
318
+
319
+ return { errors };
320
320
  }
321
321
 
322
- return { errors };
323
- }
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
+ }
324
334
 
325
- /**
326
- * Validates SubAgent structure recursively
327
- */
328
- protected validateSubAgents(subAgents: AgentSpec['SubAgents']): { errors: string[]; corrected: boolean } {
329
- const errors: string[] = [];
330
- let corrected = false;
335
+ for (let i = 0; i < subAgents.length; i++) {
336
+ const subAgent = subAgents[i];
331
337
 
332
- if (!subAgents || subAgents.length === 0) {
333
- return { errors, corrected };
334
- }
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
+ }
335
343
 
336
- for (let i = 0; i < subAgents.length; i++) {
337
- const subAgent = subAgents[i];
338
-
339
- // Validate Type field
340
- if (!subAgent.Type) {
341
- errors.push(`❌ SubAgent at index ${i} is missing required "Type" field (must be "child" or "related")`);
342
- continue;
343
- }
344
-
345
- if (subAgent.Type !== 'child' && subAgent.Type !== 'related') {
346
- errors.push(`❌ SubAgent at index ${i} has invalid Type "${subAgent.Type}" (must be "child" or "related")`);
347
- continue;
348
- }
349
-
350
- // Validate SubAgent.SubAgent field
351
- if (!subAgent.SubAgent) {
352
- errors.push(`❌ SubAgent at index ${i} is missing required "SubAgent" field`);
353
- continue;
354
- }
355
-
356
- // SubAgent.ID can be empty string "" for NEW sub-agents (Builder creates them recursively)
357
- // Only validate that ID is not null/undefined
358
- if (subAgent.SubAgent.ID === null || subAgent.SubAgent.ID === undefined) {
359
- errors.push(
360
- `❌ SubAgent at index ${i} has null/undefined SubAgent.ID (use empty string "" for new sub-agents, or provide existing agent GUID)`
361
- );
362
- }
363
-
364
- // Validate SubAgent.SubAgent.Name
365
- if (!subAgent.SubAgent.Name || subAgent.SubAgent.Name.trim().length === 0) {
366
- errors.push(`❌ SubAgent at index ${i} is missing SubAgent.Name`);
367
- }
368
-
369
- // Validate TypeID for sub-agent
370
- if (!subAgent.SubAgent.TypeID) {
371
- errors.push(`❌ SubAgent[${i}] "${subAgent.SubAgent.Name}" is missing TypeID field`);
372
- } else {
373
- // Validate Loop/Flow specific requirements for sub-agents
374
- const isLoopSubAgent = subAgent.SubAgent.TypeID.includes('Loop');
375
- const isFlowSubAgent = subAgent.SubAgent.TypeID.includes('Flow');
376
-
377
- if (isLoopSubAgent) {
378
- // Loop sub-agents require at least one prompt
379
- if (!subAgent.SubAgent.Prompts || subAgent.SubAgent.Prompts.length === 0) {
380
- errors.push(`❌ Loop SubAgent[${i}] "${subAgent.SubAgent.Name}" requires at least ONE prompt in Prompts array`);
381
- }
382
- }
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
+ }
383
348
 
384
- if (isFlowSubAgent) {
385
- // Flow sub-agents MUST have empty Prompts array
386
- if (subAgent.SubAgent.Prompts && subAgent.SubAgent.Prompts.length > 0) {
387
- errors.push(
388
- `❌ Flow SubAgent[${i}] "${subAgent.SubAgent.Name}" MUST have empty Prompts array. Use Prompt steps or Loop sub-agents for LLM functionality.`
389
- );
390
- }
391
-
392
- // Flow sub-agents require Steps
393
- if (!subAgent.SubAgent.Steps || subAgent.SubAgent.Steps.length === 0) {
394
- errors.push(`❌ Flow SubAgent[${i}] "${subAgent.SubAgent.Name}" requires at least ONE step in Steps array`);
395
- }
396
- }
397
- }
398
-
399
- // Auto-correct: Add default StartingPayloadValidationMode if missing
400
- if (!subAgent.SubAgent.StartingPayloadValidationMode) {
401
- subAgent.SubAgent.StartingPayloadValidationMode = 'Fail';
402
- corrected = true;
403
- console.log(`✓ Auto-corrected: Added SubAgent[${i}].SubAgent.StartingPayloadValidationMode = "Fail"`);
404
- }
405
-
406
- // Auto-correct: Add default Status if missing
407
- if (!subAgent.SubAgent.Status) {
408
- subAgent.SubAgent.Status = 'Active';
409
- corrected = true;
410
- console.log(`✓ Auto-corrected: Added SubAgent[${i}].SubAgent.Status = "Active"`);
411
- }
412
-
413
- // Validate related-specific fields
414
- if (subAgent.Type === 'related') {
415
- // AgentRelationshipID can be empty for new relationships, it will be created on save
416
-
417
- // IMPORTANT: Related agents REQUIRE existing agent ID
418
- if (!subAgent.SubAgent.ID || subAgent.SubAgent.ID === '') {
419
- errors.push(
420
- `❌ Related SubAgent[${i}] "${subAgent.SubAgent.Name}" must have existing agent ID. Use Find Candidate Agents to get the ID.`
421
- );
422
- }
349
+ // Validate SubAgent.SubAgent field
350
+ if (!subAgent.SubAgent) {
351
+ errors.push(`❌ SubAgent at index ${i} is missing required "SubAgent" field`);
352
+ continue;
353
+ }
423
354
 
424
- // IMPORTANT: Related agents REQUIRE mapping fields
425
- if (!subAgent.SubAgentInputMapping && !subAgent.SubAgentOutputMapping) {
426
- errors.push(
427
- `❌ Related SubAgent[${i}] "${subAgent.SubAgent.Name}" must have at least SubAgentInputMapping or SubAgentOutputMapping`
428
- );
429
- }
430
- }
431
-
432
- // Recursively validate nested sub-agents
433
- if (subAgent.SubAgent.SubAgents && subAgent.SubAgent.SubAgents.length > 0) {
434
- const nestedValidation = this.validateSubAgents(subAgent.SubAgent.SubAgents);
435
- if (nestedValidation.errors.length > 0) {
436
- // Prefix nested errors with parent context
437
- const prefixedErrors = nestedValidation.errors.map((err) => `SubAgent[${i}] "${subAgent.SubAgent.Name}" -> ${err}`);
438
- errors.push(...prefixedErrors);
439
- }
440
- if (nestedValidation.corrected) {
441
- corrected = true;
442
- }
443
- }
444
- }
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
+ }
445
380
 
446
- return { errors, corrected };
447
- }
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
+ }
448
386
 
449
- /**
450
- * Formats validation errors into a clear, actionable message
451
- */
452
- protected formatValidationErrors(errors: string[]): string {
453
- if (errors.length === 0) {
454
- return '';
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') {
410
+ // AgentRelationshipID can be empty for new relationships, it will be created on save
411
+
412
+ // IMPORTANT: Related agents REQUIRE existing agent ID
413
+ if (!subAgent.SubAgent.ID || subAgent.SubAgent.ID === '') {
414
+ errors.push(`❌ Related SubAgent[${i}] "${subAgent.SubAgent.Name}" must have existing agent ID. Use Find Candidate Agents to get the ID.`);
415
+ }
416
+
417
+ // IMPORTANT: Related agents REQUIRE mapping fields
418
+ if (!subAgent.SubAgentInputMapping && !subAgent.SubAgentOutputMapping) {
419
+ errors.push(`❌ Related SubAgent[${i}] "${subAgent.SubAgent.Name}" must have at least SubAgentInputMapping or SubAgentOutputMapping`);
420
+ }
421
+ }
422
+
423
+ // Recursively validate nested sub-agents
424
+ if (subAgent.SubAgent.SubAgents && subAgent.SubAgent.SubAgents.length > 0) {
425
+ const nestedValidation = this.validateSubAgents(subAgent.SubAgent.SubAgents);
426
+ if (nestedValidation.errors.length > 0) {
427
+ // Prefix nested errors with parent context
428
+ const prefixedErrors = nestedValidation.errors.map(
429
+ err => `SubAgent[${i}] "${subAgent.SubAgent.Name}" -> ${err}`
430
+ );
431
+ errors.push(...prefixedErrors);
432
+ }
433
+ if (nestedValidation.corrected) {
434
+ corrected = true;
435
+ }
436
+ }
437
+ }
438
+
439
+ return { errors, corrected };
455
440
  }
456
441
 
457
- const header = '## AgentSpec Validation Errors\n\nThe generated AgentSpec has the following issues that must be fixed:\n\n';
458
- const errorList = errors.map((err, idx) => `${idx + 1}. ${err}`).join('\n');
459
- const footer = '\n\n**Please fix these issues and regenerate the AgentSpec.**';
442
+ /**
443
+ * Formats validation errors into a clear, actionable message
444
+ */
445
+ protected formatValidationErrors(errors: string[]): string {
446
+ if (errors.length === 0) {
447
+ return '';
448
+ }
460
449
 
461
- return header + errorList + footer;
462
- }
450
+ const header = '## AgentSpec Validation Errors\n\nThe generated AgentSpec has the following issues that must be fixed:\n\n';
451
+ const errorList = errors.map((err, idx) => `${idx + 1}. ${err}`).join('\n');
452
+ const footer = '\n\n**Please fix these issues and regenerate the AgentSpec.**';
453
+
454
+ return header + errorList + footer;
455
+ }
463
456
  }