@memberjunction/ai-agent-manager 2.111.1 → 2.112.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 +6 -6
- package/dist/agent-spec-sync.d.ts +1 -1
- package/dist/agent-spec-sync.d.ts.map +1 -1
- package/dist/agent-spec-sync.js +76 -83
- package/dist/agent-spec-sync.js.map +1 -1
- package/dist/agents/architect-agent.d.ts.map +1 -1
- package/dist/agents/architect-agent.js +14 -18
- package/dist/agents/architect-agent.js.map +1 -1
- package/dist/agents/builder-agent.d.ts.map +1 -1
- package/dist/agents/builder-agent.js +7 -7
- package/dist/agents/builder-agent.js.map +1 -1
- package/package.json +5 -6
- package/src/agent-spec-sync.ts +1733 -1799
- package/src/agents/architect-agent.ts +385 -378
- package/src/agents/builder-agent.ts +124 -133
|
@@ -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/
|
|
4
|
+
import { Metadata, RunView } from '@memberjunction/global';
|
|
5
5
|
import { RegisterClass } from '@memberjunction/global';
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -22,435 +22,442 @@ import { RegisterClass } from '@memberjunction/global';
|
|
|
22
22
|
*/
|
|
23
23
|
@RegisterClass(BaseAgent, 'AgentArchitectAgent')
|
|
24
24
|
export class AgentArchitectAgent extends BaseAgent {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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;
|
|
36
48
|
}
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
|
|
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
|
+
// Apply payload changes to get the spec that would be sent forward
|
|
51
|
+
const payloadWithChanges = this.applyPayloadChanges<P>(currentPayload, nextStep);
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
// The payload IS the AgentSpec
|
|
54
|
+
const agentSpec = payloadWithChanges as AgentSpec;
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
const agentSpec = payloadWithChanges as AgentSpec;
|
|
56
|
+
console.log('🏗️ Architect Agent: Validating AgentSpec:', agentSpec ? `Name="${agentSpec.Name}"` : 'agentSpec is null/undefined');
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
// Validate the AgentSpec
|
|
59
|
+
const validation = await this.validateAgentSpec(agentSpec, params);
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
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
|
+
}
|
|
61
71
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
};
|
|
71
|
-
}
|
|
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
|
+
}
|
|
72
80
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
}
|
|
81
100
|
|
|
82
|
-
|
|
83
|
-
|
|
101
|
+
const correctedSpec = { ...spec };
|
|
102
|
+
|
|
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');
|
|
84
106
|
}
|
|
85
107
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
}
|
|
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
|
+
);
|
|
117
|
+
}
|
|
102
118
|
|
|
103
|
-
|
|
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
|
+
}
|
|
104
126
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
+
}
|
|
109
137
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
} else if (correctedSpec.StartingPayloadValidationMode !== 'Fail' && correctedSpec.StartingPayloadValidationMode !== 'Warn') {
|
|
116
|
-
errors.push(`❌ StartingPayloadValidationMode must be either "Fail" or "Warn", got: "${correctedSpec.StartingPayloadValidationMode}"`);
|
|
117
|
-
}
|
|
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
|
+
}
|
|
118
143
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
+
}
|
|
126
152
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (subAgentValidation.errors.length > 0) {
|
|
131
|
-
errors.push(...subAgentValidation.errors);
|
|
132
|
-
}
|
|
133
|
-
if (subAgentValidation.corrected) {
|
|
134
|
-
corrected = true;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
153
|
+
// 5. Validate agent type-specific requirements
|
|
154
|
+
const isLoopAgent = specWithType.TypeID?.includes('Loop');
|
|
155
|
+
const isFlowAgent = specWithType.TypeID?.includes('Flow');
|
|
137
156
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
}
|
|
143
163
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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');
|
|
151
180
|
}
|
|
181
|
+
}
|
|
152
182
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
}
|
|
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');
|
|
162
188
|
}
|
|
163
189
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
}
|
|
190
|
+
// Validate each step based on its type
|
|
191
|
+
for (let i = 0; i < correctedSpec.Steps.length; i++) {
|
|
192
|
+
const step = correctedSpec.Steps[i];
|
|
169
193
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
}
|
|
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`);
|
|
179
198
|
}
|
|
180
199
|
|
|
181
|
-
// Validate
|
|
182
|
-
if (
|
|
183
|
-
|
|
184
|
-
if (
|
|
185
|
-
|
|
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);
|
|
186
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
|
+
}
|
|
210
|
+
}
|
|
187
211
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
}
|
|
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);
|
|
243
217
|
}
|
|
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
|
+
}
|
|
244
222
|
}
|
|
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
|
+
}
|
|
240
|
+
}
|
|
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
|
+
}
|
|
245
249
|
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
246
252
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
}
|
|
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
|
+
}
|
|
257
257
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
correctedSpec: corrected ? correctedSpec : undefined
|
|
261
|
-
};
|
|
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}"`);
|
|
262
260
|
}
|
|
263
261
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
params: ExecuteAgentParams
|
|
270
|
-
): Promise<{ errors: string[] }> {
|
|
271
|
-
const errors: string[] = [];
|
|
272
|
-
|
|
273
|
-
if (!actions || actions.length === 0) {
|
|
274
|
-
return { errors };
|
|
275
|
-
}
|
|
262
|
+
return {
|
|
263
|
+
errors,
|
|
264
|
+
correctedSpec: corrected ? correctedSpec : undefined,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
276
267
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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[] = [];
|
|
282
273
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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
|
-
}
|
|
274
|
+
if (!actions || actions.length === 0) {
|
|
275
|
+
return { errors };
|
|
276
|
+
}
|
|
300
277
|
|
|
301
|
-
|
|
278
|
+
try {
|
|
279
|
+
const rv = new RunView();
|
|
280
|
+
const actionIds = actions.map((a) => a.ActionID).filter((id) => id && id.trim().length > 0);
|
|
302
281
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
+
}
|
|
309
302
|
|
|
310
|
-
|
|
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
|
-
}
|
|
303
|
+
const foundIds = new Set(result.Results.map((a) => a.ID));
|
|
314
304
|
|
|
315
|
-
|
|
316
|
-
|
|
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;
|
|
317
310
|
}
|
|
318
311
|
|
|
319
|
-
|
|
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 };
|
|
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
|
+
);
|
|
333
316
|
}
|
|
317
|
+
}
|
|
318
|
+
} catch (error: any) {
|
|
319
|
+
errors.push(`❌ Error validating actions: ${error?.message || String(error)}`);
|
|
320
|
+
}
|
|
334
321
|
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
}
|
|
322
|
+
return { errors };
|
|
323
|
+
}
|
|
407
324
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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;
|
|
411
331
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
}
|
|
332
|
+
if (!subAgents || subAgents.length === 0) {
|
|
333
|
+
return { errors, corrected };
|
|
334
|
+
}
|
|
422
335
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
+
}
|
|
437
382
|
}
|
|
438
383
|
|
|
439
|
-
|
|
440
|
-
|
|
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
|
+
}
|
|
441
423
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
return '';
|
|
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
|
+
);
|
|
448
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
|
+
}
|
|
449
445
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const footer = '\n\n**Please fix these issues and regenerate the AgentSpec.**';
|
|
446
|
+
return { errors, corrected };
|
|
447
|
+
}
|
|
453
448
|
|
|
454
|
-
|
|
449
|
+
/**
|
|
450
|
+
* Formats validation errors into a clear, actionable message
|
|
451
|
+
*/
|
|
452
|
+
protected formatValidationErrors(errors: string[]): string {
|
|
453
|
+
if (errors.length === 0) {
|
|
454
|
+
return '';
|
|
455
455
|
}
|
|
456
|
+
|
|
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.**';
|
|
460
|
+
|
|
461
|
+
return header + errorList + footer;
|
|
462
|
+
}
|
|
456
463
|
}
|