@memberjunction/ai-agent-manager 2.112.0 → 2.113.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 +24 -1
- 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 +83 -76
- 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 +18 -14
- 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/dist/agents/planning-designer-agent.d.ts +37 -0
- package/dist/agents/planning-designer-agent.d.ts.map +1 -0
- package/dist/agents/planning-designer-agent.js +160 -0
- package/dist/agents/planning-designer-agent.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +6 -5
- package/src/agent-spec-sync.ts +1799 -1733
- package/src/agents/architect-agent.ts +378 -385
- package/src/agents/builder-agent.ts +133 -124
- package/src/agents/planning-designer-agent.ts +179 -0
- package/src/index.ts +3 -1
|
@@ -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/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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
54
|
-
|
|
51
|
+
// Apply payload changes to get the spec that would be sent forward
|
|
52
|
+
const payloadWithChanges = this.applyPayloadChanges<P>(currentPayload, nextStep);
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
// The payload IS the AgentSpec
|
|
55
|
+
const agentSpec = payloadWithChanges as AgentSpec;
|
|
57
56
|
|
|
58
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
|
191
|
-
|
|
192
|
-
|
|
153
|
+
// 5. Validate agent type-specific requirements
|
|
154
|
+
const isLoopAgent = specWithType.TypeID?.includes('Loop');
|
|
155
|
+
const isFlowAgent = specWithType.TypeID?.includes('Flow');
|
|
193
156
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (!
|
|
197
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
//
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
259
|
-
|
|
258
|
+
return {
|
|
259
|
+
errors,
|
|
260
|
+
correctedSpec: corrected ? correctedSpec : undefined
|
|
261
|
+
};
|
|
260
262
|
}
|
|
261
263
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
283
|
+
if (actionIds.length === 0) {
|
|
284
|
+
errors.push('❌ All actions must have an ActionID');
|
|
285
|
+
return { errors };
|
|
286
|
+
}
|
|
277
287
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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
|
-
|
|
319
|
-
errors.push(`❌ Error validating actions: ${error?.message || String(error)}`);
|
|
318
|
+
|
|
319
|
+
return { errors };
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
|
|
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
|
-
|
|
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
|
-
|
|
333
|
-
|
|
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
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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
|
-
|
|
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
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
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
|
}
|