@memberjunction/ai-agent-manager 2.108.0 → 2.110.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +35 -0
- package/dist/agent-spec-sync.d.ts +117 -7
- package/dist/agent-spec-sync.d.ts.map +1 -1
- package/dist/agent-spec-sync.js +599 -50
- package/dist/agent-spec-sync.js.map +1 -1
- package/dist/agents/architect-agent.d.ts +52 -0
- package/dist/agents/architect-agent.d.ts.map +1 -0
- package/dist/agents/architect-agent.js +384 -0
- package/dist/agents/architect-agent.js.map +1 -0
- package/dist/agents/builder-agent.d.ts +31 -0
- package/dist/agents/builder-agent.d.ts.map +1 -0
- package/dist/agents/builder-agent.js +131 -0
- package/dist/agents/builder-agent.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/old/agent-definition.interface.d.ts +64 -0
- package/dist/old/agent-definition.interface.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/agent-spec-sync.ts +784 -56
- package/src/agents/architect-agent.ts +447 -0
- package/src/agents/builder-agent.ts +151 -0
- package/src/index.ts +21 -2
- package/src/old/agent-definition.interface.ts +59 -0
- package/tsconfig.json +3 -1
package/dist/agent-spec-sync.js
CHANGED
|
@@ -75,6 +75,11 @@ class AgentSpecSync {
|
|
|
75
75
|
* @private
|
|
76
76
|
*/
|
|
77
77
|
this._isDirty = false;
|
|
78
|
+
/**
|
|
79
|
+
* Tracks all database mutations performed during save operations
|
|
80
|
+
* @private
|
|
81
|
+
*/
|
|
82
|
+
this._mutations = [];
|
|
78
83
|
if (spec) {
|
|
79
84
|
this.spec = this.initializeSpec(spec);
|
|
80
85
|
this._isDirty = true;
|
|
@@ -84,13 +89,39 @@ class AgentSpecSync {
|
|
|
84
89
|
this.spec = {
|
|
85
90
|
ID: '', // Will be set on save if empty
|
|
86
91
|
Name: '',
|
|
87
|
-
StartingPayloadValidationMode:
|
|
92
|
+
StartingPayloadValidationMode: 'Fail',
|
|
88
93
|
Actions: [],
|
|
89
94
|
SubAgents: []
|
|
90
95
|
};
|
|
91
96
|
}
|
|
92
97
|
this._contextUser = contextUser;
|
|
93
98
|
}
|
|
99
|
+
// ===== MUTATION TRACKING METHODS =====
|
|
100
|
+
/**
|
|
101
|
+
* Track a database mutation performed during save operation
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
trackMutation(entity, operation, id, description) {
|
|
105
|
+
this._mutations.push({
|
|
106
|
+
Entity: entity,
|
|
107
|
+
Operation: operation,
|
|
108
|
+
ID: id,
|
|
109
|
+
Description: description
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get all mutations tracked during the last save operation
|
|
114
|
+
* @returns Array of mutations
|
|
115
|
+
*/
|
|
116
|
+
getMutations() {
|
|
117
|
+
return [...this._mutations];
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Clear all tracked mutations
|
|
121
|
+
*/
|
|
122
|
+
clearMutations() {
|
|
123
|
+
this._mutations = [];
|
|
124
|
+
}
|
|
94
125
|
// ===== STATIC FACTORY METHODS =====
|
|
95
126
|
/**
|
|
96
127
|
* Load an agent and its complete hierarchy from the database by ID.
|
|
@@ -208,7 +239,8 @@ class AgentSpecSync {
|
|
|
208
239
|
throw new Error(`Agent with ID ${agentId} not found`);
|
|
209
240
|
}
|
|
210
241
|
// Step 2: Batch load related entities using RunViews for optimal performance
|
|
211
|
-
|
|
242
|
+
// Note: Load paths separately after steps to avoid hardcoded view name in subquery
|
|
243
|
+
const [actionsResult, childAgentsResult, relatedAgentsResult, promptsResult, stepsResult] = await rv.RunViews([
|
|
212
244
|
{
|
|
213
245
|
EntityName: 'AI Agent Actions',
|
|
214
246
|
ExtraFilter: `AgentID='${agentId}'`,
|
|
@@ -226,6 +258,18 @@ class AgentSpecSync {
|
|
|
226
258
|
ExtraFilter: `AgentID='${agentId}' AND Status='Active'`,
|
|
227
259
|
OrderBy: '__mj_CreatedAt',
|
|
228
260
|
ResultType: 'entity_object'
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
EntityName: 'MJ: AI Agent Prompts',
|
|
264
|
+
ExtraFilter: `AgentID='${agentId}'`,
|
|
265
|
+
OrderBy: 'ExecutionOrder',
|
|
266
|
+
ResultType: 'entity_object'
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
EntityName: 'MJ: AI Agent Steps',
|
|
270
|
+
ExtraFilter: `AgentID='${agentId}'`,
|
|
271
|
+
OrderBy: 'Name',
|
|
272
|
+
ResultType: 'entity_object'
|
|
229
273
|
}
|
|
230
274
|
], this._contextUser);
|
|
231
275
|
// Check for errors
|
|
@@ -238,13 +282,84 @@ class AgentSpecSync {
|
|
|
238
282
|
if (!relatedAgentsResult.Success) {
|
|
239
283
|
throw new Error(`Failed to load related agents: ${relatedAgentsResult.ErrorMessage}`);
|
|
240
284
|
}
|
|
285
|
+
if (!promptsResult.Success) {
|
|
286
|
+
throw new Error(`Failed to load agent prompts: ${promptsResult.ErrorMessage}`);
|
|
287
|
+
}
|
|
288
|
+
if (!stepsResult.Success) {
|
|
289
|
+
throw new Error(`Failed to load agent steps: ${stepsResult.ErrorMessage}`);
|
|
290
|
+
}
|
|
291
|
+
// Step 2b: Load paths separately using step IDs to avoid hardcoded view name
|
|
292
|
+
let pathsResult;
|
|
293
|
+
const steps = stepsResult.Results || [];
|
|
294
|
+
if (steps.length > 0) {
|
|
295
|
+
const stepIds = steps.map((s) => `'${s.ID}'`).join(',');
|
|
296
|
+
pathsResult = await rv.RunView({
|
|
297
|
+
EntityName: 'MJ: AI Agent Step Paths',
|
|
298
|
+
ExtraFilter: `OriginStepID IN (${stepIds})`,
|
|
299
|
+
OrderBy: 'Priority DESC',
|
|
300
|
+
ResultType: 'entity_object'
|
|
301
|
+
}, this._contextUser);
|
|
302
|
+
if (!pathsResult.Success) {
|
|
303
|
+
throw new Error(`Failed to load step paths: ${pathsResult.ErrorMessage}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
// No steps, so no paths
|
|
308
|
+
pathsResult = {
|
|
309
|
+
Success: true,
|
|
310
|
+
Results: [],
|
|
311
|
+
RowCount: 0
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
// Step 2c: Load full AI Prompt records to get PromptText, PromptRole, PromptPosition
|
|
315
|
+
let fullPromptsResult;
|
|
316
|
+
const agentPrompts = promptsResult.Results || [];
|
|
317
|
+
if (agentPrompts.length > 0) {
|
|
318
|
+
const promptIds = agentPrompts.map((p) => `'${p.PromptID}'`).join(',');
|
|
319
|
+
fullPromptsResult = await rv.RunView({
|
|
320
|
+
EntityName: 'AI Prompts',
|
|
321
|
+
ExtraFilter: `ID IN (${promptIds})`,
|
|
322
|
+
OrderBy: 'Name',
|
|
323
|
+
ResultType: 'entity_object'
|
|
324
|
+
}, this._contextUser);
|
|
325
|
+
if (!fullPromptsResult.Success) {
|
|
326
|
+
throw new Error(`Failed to load AI prompts: ${fullPromptsResult.ErrorMessage}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
// No prompts
|
|
331
|
+
fullPromptsResult = {
|
|
332
|
+
Success: true,
|
|
333
|
+
Results: [],
|
|
334
|
+
RowCount: 0
|
|
335
|
+
};
|
|
336
|
+
}
|
|
241
337
|
// Step 3: Map entities to raw spec format
|
|
242
|
-
this.spec = this.mapEntitiesToRawSpec(agentEntity, actionsResult.Results || [], childAgentsResult.Results || [], relatedAgentsResult.Results || []);
|
|
338
|
+
this.spec = this.mapEntitiesToRawSpec(agentEntity, actionsResult.Results || [], childAgentsResult.Results || [], relatedAgentsResult.Results || [], promptsResult.Results || [], fullPromptsResult.Results || [], stepsResult.Results || [], pathsResult.Results || []);
|
|
243
339
|
// Step 4: Recursively load sub-agents if requested
|
|
244
340
|
if (includeSubAgents && this.spec.SubAgents && this.spec.SubAgents.length > 0) {
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
341
|
+
// Recursively load complete specs for all sub-agents
|
|
342
|
+
for (const subAgentSpec of this.spec.SubAgents) {
|
|
343
|
+
if (subAgentSpec.SubAgent && subAgentSpec.SubAgent.ID) {
|
|
344
|
+
try {
|
|
345
|
+
// Load the complete sub-agent spec recursively
|
|
346
|
+
const subAgentSync = await AgentSpecSync.LoadFromDatabase(subAgentSpec.SubAgent.ID, this._contextUser, true // Recursively load nested sub-agents too
|
|
347
|
+
);
|
|
348
|
+
// Replace the minimal SubAgent with the complete spec
|
|
349
|
+
const fullSubAgentSpec = subAgentSync.toJSON();
|
|
350
|
+
// Preserve the relationship metadata while adding full spec details
|
|
351
|
+
subAgentSpec.SubAgent = {
|
|
352
|
+
...fullSubAgentSpec,
|
|
353
|
+
// Ensure we preserve any relationship-specific overrides
|
|
354
|
+
StartingPayloadValidationMode: subAgentSpec.SubAgent.StartingPayloadValidationMode || fullSubAgentSpec.StartingPayloadValidationMode
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
(0, core_1.LogError)(`Failed to load sub-agent ${subAgentSpec.SubAgent.ID}: ${error instanceof Error ? error.message : String(error)}`);
|
|
359
|
+
// Continue with partial data rather than failing completely
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
248
363
|
}
|
|
249
364
|
this._isLoaded = true;
|
|
250
365
|
this._isDirty = false;
|
|
@@ -260,14 +375,20 @@ class AgentSpecSync {
|
|
|
260
375
|
* @param actions - Array of agent action entities
|
|
261
376
|
* @param childAgents - Array of child agent entities (ParentID-based)
|
|
262
377
|
* @param relatedAgents - Array of related agent relationship entities
|
|
378
|
+
* @param agentPrompts - Array of agent prompt junction entities
|
|
379
|
+
* @param fullPrompts - Array of full AI Prompt entities with PromptText, PromptRole, PromptPosition
|
|
380
|
+
* @param steps - Array of agent step entities (for Flow agents)
|
|
381
|
+
* @param paths - Array of step path entities (for Flow agents)
|
|
263
382
|
* @returns Fully populated AgentSpec object
|
|
264
383
|
*/
|
|
265
|
-
mapEntitiesToRawSpec(agent, actions, childAgents, relatedAgents) {
|
|
384
|
+
mapEntitiesToRawSpec(agent, actions, childAgents, relatedAgents, agentPrompts, fullPrompts, steps, paths) {
|
|
266
385
|
// Map all agent fields to spec
|
|
267
386
|
const spec = {
|
|
268
387
|
ID: agent.ID,
|
|
269
388
|
Name: agent.Name || '',
|
|
270
389
|
Description: agent.Description || undefined,
|
|
390
|
+
TypeID: agent.TypeID || undefined,
|
|
391
|
+
Status: agent.Status || undefined,
|
|
271
392
|
IconClass: agent.IconClass || undefined,
|
|
272
393
|
LogoURL: agent.LogoURL || undefined,
|
|
273
394
|
ParentID: agent.ParentID || undefined,
|
|
@@ -299,13 +420,22 @@ class AgentSpecSync {
|
|
|
299
420
|
DefaultArtifactTypeID: agent.DefaultArtifactTypeID || undefined,
|
|
300
421
|
OwnerUserID: agent.OwnerUserID || undefined,
|
|
301
422
|
InvocationMode: agent.InvocationMode,
|
|
423
|
+
// Requirements and design documentation
|
|
424
|
+
FunctionalRequirements: agent.FunctionalRequirements || null,
|
|
425
|
+
TechnicalDesign: agent.TechnicalDesign || null,
|
|
302
426
|
// Map actions
|
|
303
427
|
Actions: actions.map(action => this.mapActionEntityToSpec(action)),
|
|
304
428
|
// Map sub-agents (both child and related)
|
|
305
429
|
SubAgents: [
|
|
306
430
|
...childAgents.map(child => this.mapChildAgentToSpec(child)),
|
|
307
431
|
...relatedAgents.map(rel => this.mapRelatedAgentToSpec(rel))
|
|
308
|
-
]
|
|
432
|
+
],
|
|
433
|
+
// Map prompts (agent-level prompts for Loop agents)
|
|
434
|
+
Prompts: agentPrompts.map(agentPrompt => this.mapPromptEntityToSpec(agentPrompt, fullPrompts)),
|
|
435
|
+
// Map steps (for Flow agents)
|
|
436
|
+
Steps: steps.map(step => this.mapStepEntityToSpec(step)),
|
|
437
|
+
// Map paths (for Flow agents)
|
|
438
|
+
Paths: paths.map(path => this.mapPathEntityToSpec(path))
|
|
309
439
|
};
|
|
310
440
|
return spec;
|
|
311
441
|
}
|
|
@@ -339,7 +469,11 @@ class AgentSpecSync {
|
|
|
339
469
|
mapChildAgentToSpec(childAgent) {
|
|
340
470
|
return {
|
|
341
471
|
Type: 'child',
|
|
342
|
-
|
|
472
|
+
SubAgent: {
|
|
473
|
+
ID: childAgent.ID,
|
|
474
|
+
Name: childAgent.Name || '',
|
|
475
|
+
StartingPayloadValidationMode: 'Fail'
|
|
476
|
+
}
|
|
343
477
|
};
|
|
344
478
|
}
|
|
345
479
|
/**
|
|
@@ -352,13 +486,82 @@ class AgentSpecSync {
|
|
|
352
486
|
mapRelatedAgentToSpec(relationship) {
|
|
353
487
|
return {
|
|
354
488
|
Type: 'related',
|
|
355
|
-
|
|
489
|
+
SubAgent: {
|
|
490
|
+
ID: relationship.SubAgentID,
|
|
491
|
+
Name: relationship.SubAgent || '',
|
|
492
|
+
StartingPayloadValidationMode: 'Fail'
|
|
493
|
+
},
|
|
356
494
|
AgentRelationshipID: relationship.ID,
|
|
357
495
|
SubAgentInputMapping: this.parseJsonField(relationship.SubAgentInputMapping),
|
|
358
496
|
SubAgentOutputMapping: this.parseJsonField(relationship.SubAgentOutputMapping),
|
|
359
497
|
SubAgentContextPaths: this.parseJsonField(relationship.SubAgentContextPaths)
|
|
360
498
|
};
|
|
361
499
|
}
|
|
500
|
+
/**
|
|
501
|
+
* Map AIAgentPromptEntity junction to AgentPromptSpec format.
|
|
502
|
+
*
|
|
503
|
+
* @private
|
|
504
|
+
* @param agentPrompt - The agent prompt junction entity (has PromptID, ExecutionOrder)
|
|
505
|
+
* @param fullPrompts - Array of full AI Prompt entities
|
|
506
|
+
* @returns Mapped prompt spec
|
|
507
|
+
*/
|
|
508
|
+
mapPromptEntityToSpec(agentPrompt, fullPrompts) {
|
|
509
|
+
// Find the full prompt data by matching PromptID
|
|
510
|
+
const fullPrompt = fullPrompts.find((p) => p.ID === agentPrompt.PromptID);
|
|
511
|
+
if (!fullPrompt) {
|
|
512
|
+
// Fallback if prompt not found
|
|
513
|
+
return {
|
|
514
|
+
PromptID: agentPrompt.PromptID || '',
|
|
515
|
+
PromptText: '',
|
|
516
|
+
PromptRole: 'System',
|
|
517
|
+
PromptPosition: 'First'
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
PromptID: fullPrompt.ID || '',
|
|
522
|
+
PromptText: fullPrompt.TemplateText || '',
|
|
523
|
+
PromptRole: fullPrompt.PromptRole || 'System',
|
|
524
|
+
PromptPosition: fullPrompt.PromptPosition || 'First'
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Map AIAgentStepEntity to AgentStep format.
|
|
529
|
+
*
|
|
530
|
+
* @private
|
|
531
|
+
* @param step - The agent step entity
|
|
532
|
+
* @returns Mapped step spec
|
|
533
|
+
*/
|
|
534
|
+
mapStepEntityToSpec(step) {
|
|
535
|
+
return {
|
|
536
|
+
ID: step.ID,
|
|
537
|
+
Name: step.Name,
|
|
538
|
+
Description: step.Description || undefined,
|
|
539
|
+
StepType: step.StepType,
|
|
540
|
+
StartingStep: step.StartingStep,
|
|
541
|
+
ActionID: step.ActionID || undefined,
|
|
542
|
+
SubAgentID: step.SubAgentID || undefined,
|
|
543
|
+
PromptID: step.PromptID || undefined,
|
|
544
|
+
ActionInputMapping: this.parseJsonField(step.ActionInputMapping),
|
|
545
|
+
ActionOutputMapping: this.parseJsonField(step.ActionOutputMapping)
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Map AIAgentStepPathEntity to AgentStepPath format.
|
|
550
|
+
*
|
|
551
|
+
* @private
|
|
552
|
+
* @param path - The agent step path entity
|
|
553
|
+
* @returns Mapped path spec
|
|
554
|
+
*/
|
|
555
|
+
mapPathEntityToSpec(path) {
|
|
556
|
+
return {
|
|
557
|
+
ID: path.ID,
|
|
558
|
+
OriginStepID: path.OriginStepID,
|
|
559
|
+
DestinationStepID: path.DestinationStepID,
|
|
560
|
+
Condition: path.Condition || undefined,
|
|
561
|
+
Description: path.Description || undefined,
|
|
562
|
+
Priority: path.Priority
|
|
563
|
+
};
|
|
564
|
+
}
|
|
362
565
|
// ===== SAVING METHODS =====
|
|
363
566
|
/**
|
|
364
567
|
* Save the current spec to the database.
|
|
@@ -370,7 +573,7 @@ class AgentSpecSync {
|
|
|
370
573
|
* database schema are executed, and any failures will prevent the save.
|
|
371
574
|
*
|
|
372
575
|
* @param validate - Whether to validate before saving (default: true)
|
|
373
|
-
* @returns Promise resolving to
|
|
576
|
+
* @returns Promise resolving to result with agent ID, success flag, and all mutations performed
|
|
374
577
|
* @throws {Error} If validation fails or save operation fails
|
|
375
578
|
*
|
|
376
579
|
* @example
|
|
@@ -380,24 +583,51 @@ class AgentSpecSync {
|
|
|
380
583
|
* InvocationMode: 'Any'
|
|
381
584
|
* }, contextUser);
|
|
382
585
|
*
|
|
383
|
-
* const
|
|
384
|
-
* console.log('Saved with ID:', agentId);
|
|
586
|
+
* const result = await spec.SaveToDatabase();
|
|
587
|
+
* console.log('Saved with ID:', result.agentId);
|
|
588
|
+
* console.log('Mutations:', result.mutations.length);
|
|
385
589
|
* ```
|
|
386
590
|
*/
|
|
387
591
|
async SaveToDatabase(validate = true) {
|
|
592
|
+
// Clear previous mutations
|
|
593
|
+
this.clearMutations();
|
|
388
594
|
if (!this._isDirty && this._isLoaded) {
|
|
389
595
|
// No changes to save
|
|
390
|
-
return
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
596
|
+
return {
|
|
597
|
+
agentId: this.spec.ID,
|
|
598
|
+
success: true,
|
|
599
|
+
mutations: []
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
try {
|
|
603
|
+
// Step 1: Save main agent entity
|
|
604
|
+
const agentId = await this.saveAgentEntity(validate);
|
|
605
|
+
// Step 2: Save actions
|
|
606
|
+
await this.saveActions(agentId);
|
|
607
|
+
// Step 3: Save sub-agents (both child and related)
|
|
608
|
+
await this.saveSubAgents(agentId);
|
|
609
|
+
// Step 4: Save prompts
|
|
610
|
+
await this.savePrompts(agentId);
|
|
611
|
+
// Step 5: Save steps (Flow agents only)
|
|
612
|
+
await this.saveSteps(agentId);
|
|
613
|
+
// Step 6: Save step paths (Flow agents only)
|
|
614
|
+
await this.saveStepPaths(agentId);
|
|
615
|
+
this._isDirty = false;
|
|
616
|
+
this._isLoaded = true;
|
|
617
|
+
return {
|
|
618
|
+
agentId,
|
|
619
|
+
success: true,
|
|
620
|
+
mutations: this.getMutations()
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
// Return failure with whatever mutations were completed before error
|
|
625
|
+
return {
|
|
626
|
+
agentId: this.spec.ID || '',
|
|
627
|
+
success: false,
|
|
628
|
+
mutations: this.getMutations()
|
|
629
|
+
};
|
|
630
|
+
}
|
|
401
631
|
}
|
|
402
632
|
/**
|
|
403
633
|
* Save the main AIAgent entity.
|
|
@@ -410,6 +640,8 @@ class AgentSpecSync {
|
|
|
410
640
|
async saveAgentEntity(validate) {
|
|
411
641
|
const md = new core_1.Metadata();
|
|
412
642
|
const agentEntity = await md.GetEntityObject('AI Agents', this._contextUser);
|
|
643
|
+
// Track if this is an update or create
|
|
644
|
+
const isUpdate = !!this.spec.ID;
|
|
413
645
|
// If ID exists, load existing record
|
|
414
646
|
if (this.spec.ID) {
|
|
415
647
|
const loaded = await agentEntity.Load(this.spec.ID);
|
|
@@ -425,6 +657,17 @@ class AgentSpecSync {
|
|
|
425
657
|
agentEntity.ParentID = this.spec.ParentID || null;
|
|
426
658
|
agentEntity.DriverClass = this.spec.DriverClass || null;
|
|
427
659
|
agentEntity.ModelSelectionMode = this.spec.ModelSelectionMode || 'Agent Type';
|
|
660
|
+
// Handle TypeID - supports lookup references or direct GUID
|
|
661
|
+
if (this.spec.TypeID) {
|
|
662
|
+
agentEntity.TypeID = this.spec.TypeID;
|
|
663
|
+
}
|
|
664
|
+
// Handle Status - defaults to Active if not specified
|
|
665
|
+
if (this.spec.Status) {
|
|
666
|
+
agentEntity.Status = this.spec.Status;
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
agentEntity.Status = 'Active';
|
|
670
|
+
}
|
|
428
671
|
// Serialize JSON fields
|
|
429
672
|
agentEntity.PayloadDownstreamPaths = JSON.stringify(this.spec.PayloadDownstreamPaths || ['*']);
|
|
430
673
|
agentEntity.PayloadUpstreamPaths = JSON.stringify(this.spec.PayloadUpstreamPaths || ['*']);
|
|
@@ -440,7 +683,7 @@ class AgentSpecSync {
|
|
|
440
683
|
agentEntity.FinalPayloadValidationMode = this.spec.FinalPayloadValidationMode || 'Retry';
|
|
441
684
|
agentEntity.FinalPayloadValidationMaxRetries = this.spec.FinalPayloadValidationMaxRetries || 3;
|
|
442
685
|
agentEntity.StartingPayloadValidation = this.spec.StartingPayloadValidation || null;
|
|
443
|
-
agentEntity.StartingPayloadValidationMode = this.spec.StartingPayloadValidationMode
|
|
686
|
+
agentEntity.StartingPayloadValidationMode = this.spec.StartingPayloadValidationMode || 'Fail';
|
|
444
687
|
// Resource limits
|
|
445
688
|
agentEntity.MaxCostPerRun = this.spec.MaxCostPerRun || null;
|
|
446
689
|
agentEntity.MaxTokensPerRun = this.spec.MaxTokensPerRun || null;
|
|
@@ -457,6 +700,9 @@ class AgentSpecSync {
|
|
|
457
700
|
agentEntity.OwnerUserID = this.spec.OwnerUserID;
|
|
458
701
|
}
|
|
459
702
|
agentEntity.InvocationMode = this.spec.InvocationMode || 'Any';
|
|
703
|
+
// Requirements and design documentation
|
|
704
|
+
agentEntity.FunctionalRequirements = this.spec.FunctionalRequirements || null;
|
|
705
|
+
agentEntity.TechnicalDesign = this.spec.TechnicalDesign || null;
|
|
460
706
|
// Validate if requested
|
|
461
707
|
if (validate) {
|
|
462
708
|
const validation = agentEntity.Validate();
|
|
@@ -472,6 +718,8 @@ class AgentSpecSync {
|
|
|
472
718
|
}
|
|
473
719
|
// Update spec with saved ID
|
|
474
720
|
this.spec.ID = agentEntity.ID;
|
|
721
|
+
// Track the mutation
|
|
722
|
+
this.trackMutation('AI Agents', isUpdate ? 'Update' : 'Create', agentEntity.ID, `${isUpdate ? 'Updated' : 'Created'} agent: ${this.spec.Name}`);
|
|
475
723
|
return agentEntity.ID;
|
|
476
724
|
}
|
|
477
725
|
/**
|
|
@@ -492,6 +740,8 @@ class AgentSpecSync {
|
|
|
492
740
|
const md = new core_1.Metadata();
|
|
493
741
|
for (const actionSpec of this.spec.Actions) {
|
|
494
742
|
const actionEntity = await md.GetEntityObject('AI Agent Actions', this._contextUser);
|
|
743
|
+
// Track if this is an update or create
|
|
744
|
+
const isUpdate = !!actionSpec.AgentActionID;
|
|
495
745
|
// Load existing if ID provided
|
|
496
746
|
if (actionSpec.AgentActionID) {
|
|
497
747
|
await actionEntity.Load(actionSpec.AgentActionID);
|
|
@@ -512,6 +762,8 @@ class AgentSpecSync {
|
|
|
512
762
|
}
|
|
513
763
|
// Update spec with saved ID
|
|
514
764
|
actionSpec.AgentActionID = actionEntity.ID;
|
|
765
|
+
// Track the mutation
|
|
766
|
+
this.trackMutation('AI Agent Actions', isUpdate ? 'Update' : 'Create', actionEntity.ID, `${isUpdate ? 'Updated' : 'Created'} action junction for action ${actionSpec.ActionID}`);
|
|
515
767
|
}
|
|
516
768
|
}
|
|
517
769
|
/**
|
|
@@ -542,33 +794,49 @@ class AgentSpecSync {
|
|
|
542
794
|
* Save a child sub-agent (ParentID-based relationship).
|
|
543
795
|
*
|
|
544
796
|
* For child agents, the relationship is established by setting the ParentID field
|
|
545
|
-
* on the child agent entity. If the
|
|
546
|
-
* child agent
|
|
797
|
+
* on the child agent entity. If the SubAgent.ID is empty, this creates a new
|
|
798
|
+
* child agent recursively using AgentSpecSync.
|
|
799
|
+
*
|
|
800
|
+
* This enables creating complete agent hierarchies in one call - sub-agents are
|
|
801
|
+
* created first (depth-first), then parent references them via ParentID.
|
|
547
802
|
*
|
|
548
803
|
* @private
|
|
549
804
|
* @param parentId - The parent agent ID
|
|
550
805
|
* @param SubAgentSpec - The sub-agent specification
|
|
551
|
-
* @throws {Error} If child agent
|
|
806
|
+
* @throws {Error} If child agent save fails
|
|
552
807
|
*/
|
|
553
808
|
async saveChildSubAgent(parentId, SubAgentSpec) {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
809
|
+
console.log(`🔗 saveChildSubAgent: Processing child "${SubAgentSpec.SubAgent?.Name}", ID="${SubAgentSpec.SubAgent?.ID}"`);
|
|
810
|
+
// If SubAgent.ID is empty or missing, create the sub-agent recursively
|
|
811
|
+
if (!SubAgentSpec.SubAgent?.ID || SubAgentSpec.SubAgent.ID === '') {
|
|
812
|
+
console.log(`🔨 saveChildSubAgent: Creating new child sub-agent "${SubAgentSpec.SubAgent.Name}"...`);
|
|
813
|
+
// Set ParentID in the sub-agent spec before creating it
|
|
814
|
+
const childSpec = {
|
|
815
|
+
...SubAgentSpec.SubAgent,
|
|
816
|
+
ParentID: parentId
|
|
817
|
+
};
|
|
818
|
+
// Recursively create the sub-agent using AgentSpecSync
|
|
819
|
+
const childSync = new AgentSpecSync(childSpec, this._contextUser);
|
|
820
|
+
childSync.markDirty();
|
|
821
|
+
const childResult = await childSync.SaveToDatabase();
|
|
822
|
+
// Update the SubAgentSpec with the created ID so parent can reference it
|
|
823
|
+
SubAgentSpec.SubAgent.ID = childResult.agentId;
|
|
824
|
+
console.log(`✅ saveChildSubAgent: Created child sub-agent with ID: ${childResult.agentId}`);
|
|
564
825
|
}
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
826
|
+
else {
|
|
827
|
+
// SubAgent already exists - update it recursively to capture all field changes
|
|
828
|
+
console.log(`🔗 saveChildSubAgent: Updating existing child sub-agent "${SubAgentSpec.SubAgent.ID}"...`);
|
|
829
|
+
// Set ParentID in the sub-agent spec
|
|
830
|
+
const childSpec = {
|
|
831
|
+
...SubAgentSpec.SubAgent,
|
|
832
|
+
ParentID: parentId
|
|
833
|
+
};
|
|
834
|
+
// Recursively update the sub-agent using AgentSpecSync
|
|
835
|
+
// This ensures all fields including FunctionalRequirements and TechnicalDesign are updated
|
|
836
|
+
const childSync = new AgentSpecSync(childSpec, this._contextUser);
|
|
837
|
+
childSync.markDirty();
|
|
838
|
+
await childSync.SaveToDatabase();
|
|
839
|
+
console.log(`✅ saveChildSubAgent: Updated existing child sub-agent`);
|
|
572
840
|
}
|
|
573
841
|
}
|
|
574
842
|
/**
|
|
@@ -586,12 +854,13 @@ class AgentSpecSync {
|
|
|
586
854
|
const md = new core_1.Metadata();
|
|
587
855
|
const relationshipEntity = await md.GetEntityObject('MJ: AI Agent Relationships', this._contextUser);
|
|
588
856
|
// Load existing if ID provided
|
|
589
|
-
|
|
857
|
+
const isUpdate = !!SubAgentSpec.AgentRelationshipID;
|
|
858
|
+
if (isUpdate && SubAgentSpec.AgentRelationshipID) {
|
|
590
859
|
await relationshipEntity.Load(SubAgentSpec.AgentRelationshipID);
|
|
591
860
|
}
|
|
592
861
|
// Map fields
|
|
593
862
|
relationshipEntity.AgentID = agentId;
|
|
594
|
-
relationshipEntity.SubAgentID = SubAgentSpec.
|
|
863
|
+
relationshipEntity.SubAgentID = SubAgentSpec.SubAgent.ID;
|
|
595
864
|
relationshipEntity.Status = 'Active';
|
|
596
865
|
// Serialize mapping fields
|
|
597
866
|
if (SubAgentSpec.SubAgentInputMapping) {
|
|
@@ -614,11 +883,282 @@ class AgentSpecSync {
|
|
|
614
883
|
}
|
|
615
884
|
const saved = await relationshipEntity.Save();
|
|
616
885
|
if (!saved) {
|
|
617
|
-
throw new Error(`Failed to save relationship for sub-agent ${SubAgentSpec.
|
|
886
|
+
throw new Error(`Failed to save relationship for sub-agent ${SubAgentSpec.SubAgent.ID}`);
|
|
618
887
|
}
|
|
888
|
+
// Track mutation
|
|
889
|
+
this.trackMutation('AI Agent Relationships', isUpdate ? 'Update' : 'Create', relationshipEntity.ID, `${isUpdate ? 'Updated' : 'Created'} agent relationship for sub-agent ${SubAgentSpec.SubAgent.ID}`);
|
|
619
890
|
// Update spec with saved ID
|
|
620
891
|
SubAgentSpec.AgentRelationshipID = relationshipEntity.ID;
|
|
621
892
|
}
|
|
893
|
+
/**
|
|
894
|
+
* Save all prompts for this agent.
|
|
895
|
+
*
|
|
896
|
+
* Creates AIPrompt records (with template) and AIAgentPrompt junction records.
|
|
897
|
+
* Supports simplified prompt format from Architect Agent with just PromptText,
|
|
898
|
+
* PromptRole, and PromptPosition.
|
|
899
|
+
*
|
|
900
|
+
* @private
|
|
901
|
+
* @param agentId - The parent agent ID
|
|
902
|
+
* @throws {Error} If any prompt save fails
|
|
903
|
+
*/
|
|
904
|
+
async savePrompts(agentId) {
|
|
905
|
+
console.log(`💬 savePrompts: Called with agentId=${agentId}, Prompts=${this.spec.Prompts ? this.spec.Prompts.length : 'undefined'}`);
|
|
906
|
+
if (!this.spec.Prompts || this.spec.Prompts.length === 0) {
|
|
907
|
+
console.log('💬 savePrompts: No prompts to save, returning early');
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
console.log(`💬 savePrompts: Processing ${this.spec.Prompts.length} prompt(s)...`);
|
|
911
|
+
const md = new core_1.Metadata();
|
|
912
|
+
const rv = new core_1.RunView();
|
|
913
|
+
// Step 1: Create or update AIPrompt records
|
|
914
|
+
for (let i = 0; i < this.spec.Prompts.length; i++) {
|
|
915
|
+
const promptSpec = this.spec.Prompts[i];
|
|
916
|
+
// Check if this is an update (has PromptID) or create (no PromptID)
|
|
917
|
+
let promptEntity = await md.GetEntityObject('AI Prompts', this._contextUser);
|
|
918
|
+
let isUpdate = false;
|
|
919
|
+
if (promptSpec.PromptID && promptSpec.PromptID !== '') {
|
|
920
|
+
// Load existing prompt for update
|
|
921
|
+
const loaded = await promptEntity.Load(promptSpec.PromptID);
|
|
922
|
+
if (loaded) {
|
|
923
|
+
isUpdate = true;
|
|
924
|
+
console.log(`💬 savePrompts: Updating existing prompt ID: ${promptSpec.PromptID}`);
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
console.warn(`💬 savePrompts: PromptID specified but not found, creating new prompt`);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
// Set all required fields (both create and update)
|
|
931
|
+
promptEntity.Name = `${this.spec.Name} - Prompt ${i + 1}`;
|
|
932
|
+
promptEntity.Description = `Agent prompt ${i + 1} for ${this.spec.Name}`;
|
|
933
|
+
promptEntity.TypeID = 'a6da423e-f36b-1410-8dac-00021f8b792e'; // Chat type
|
|
934
|
+
promptEntity.Status = 'Active';
|
|
935
|
+
promptEntity.ResponseFormat = 'JSON';
|
|
936
|
+
// Handle prompt text - supports both string and object formats
|
|
937
|
+
if (typeof promptSpec.PromptText === 'string') {
|
|
938
|
+
promptEntity.TemplateText = promptSpec.PromptText;
|
|
939
|
+
}
|
|
940
|
+
else if (typeof promptSpec.PromptText === 'object') {
|
|
941
|
+
// Architect may send PromptText as {text: "...", json: {...}}
|
|
942
|
+
const promptTextObj = promptSpec.PromptText;
|
|
943
|
+
let combinedText = promptTextObj.text || '';
|
|
944
|
+
if (promptTextObj.json) {
|
|
945
|
+
combinedText += '\n\n```json\n' + JSON.stringify(promptTextObj.json, null, 2) + '\n```';
|
|
946
|
+
}
|
|
947
|
+
promptEntity.TemplateText = combinedText;
|
|
948
|
+
}
|
|
949
|
+
// Set prompt role and position if provided
|
|
950
|
+
if (promptSpec.PromptRole) {
|
|
951
|
+
promptEntity.PromptRole = promptSpec.PromptRole;
|
|
952
|
+
}
|
|
953
|
+
if (promptSpec.PromptPosition) {
|
|
954
|
+
promptEntity.PromptPosition = promptSpec.PromptPosition;
|
|
955
|
+
}
|
|
956
|
+
const saved = await promptEntity.Save();
|
|
957
|
+
if (!saved) {
|
|
958
|
+
throw new Error(`Failed to save prompt ${i + 1} for agent ${this.spec.Name}`);
|
|
959
|
+
}
|
|
960
|
+
// Track mutation
|
|
961
|
+
this.trackMutation('AI Prompts', isUpdate ? 'Update' : 'Create', promptEntity.ID, `${isUpdate ? 'Updated' : 'Created'} prompt: ${promptEntity.Name}`);
|
|
962
|
+
// Update the spec with the saved/updated ID
|
|
963
|
+
promptSpec.PromptID = promptEntity.ID;
|
|
964
|
+
console.log(`✅ savePrompts: ${isUpdate ? 'Updated' : 'Created'} AIPrompt with ID: ${promptEntity.ID}`);
|
|
965
|
+
// Step 2: Create or update AIAgentPrompt junction
|
|
966
|
+
// Query for existing junction by AgentID + PromptID
|
|
967
|
+
const existingJunctionResult = await rv.RunView({
|
|
968
|
+
EntityName: 'MJ: AI Agent Prompts',
|
|
969
|
+
ExtraFilter: `AgentID='${agentId}' AND PromptID='${promptEntity.ID}'`,
|
|
970
|
+
ResultType: 'entity_object'
|
|
971
|
+
}, this._contextUser);
|
|
972
|
+
let agentPromptEntity;
|
|
973
|
+
let isJunctionUpdate = false;
|
|
974
|
+
if (existingJunctionResult.Success && existingJunctionResult.Results && existingJunctionResult.Results.length > 0) {
|
|
975
|
+
// Junction exists - load it for update
|
|
976
|
+
agentPromptEntity = existingJunctionResult.Results[0];
|
|
977
|
+
isJunctionUpdate = true;
|
|
978
|
+
console.log(`💬 savePrompts: Updating existing junction for prompt ${i + 1}`);
|
|
979
|
+
}
|
|
980
|
+
else {
|
|
981
|
+
// Junction doesn't exist - create new one
|
|
982
|
+
agentPromptEntity = await md.GetEntityObject('MJ: AI Agent Prompts', this._contextUser);
|
|
983
|
+
agentPromptEntity.AgentID = agentId;
|
|
984
|
+
agentPromptEntity.PromptID = promptEntity.ID;
|
|
985
|
+
console.log(`💬 savePrompts: Creating new junction for prompt ${i + 1}`);
|
|
986
|
+
}
|
|
987
|
+
// Set/update common fields (ExecutionOrder might change)
|
|
988
|
+
agentPromptEntity.ExecutionOrder = i;
|
|
989
|
+
agentPromptEntity.Status = 'Active';
|
|
990
|
+
const junctionSaved = await agentPromptEntity.Save();
|
|
991
|
+
if (!junctionSaved) {
|
|
992
|
+
throw new Error(`Failed to save agent-prompt junction for prompt ${i + 1}`);
|
|
993
|
+
}
|
|
994
|
+
// Track mutation
|
|
995
|
+
this.trackMutation('AI Agent Prompts', isJunctionUpdate ? 'Update' : 'Create', agentPromptEntity.ID, `${isJunctionUpdate ? 'Updated' : 'Created'} agent-prompt junction for prompt ${promptEntity.ID}`);
|
|
996
|
+
console.log(`✅ savePrompts: ${isJunctionUpdate ? 'Updated' : 'Created'} AIAgentPrompt junction with ID: ${agentPromptEntity.ID}`);
|
|
997
|
+
}
|
|
998
|
+
console.log(`✅ savePrompts: Successfully saved all ${this.spec.Prompts.length} prompt(s)`);
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Save all steps for this agent (Flow agents only).
|
|
1002
|
+
*
|
|
1003
|
+
* Creates AIAgentStep records for each step in the spec. Steps define the nodes
|
|
1004
|
+
* in a Flow agent's execution graph.
|
|
1005
|
+
*
|
|
1006
|
+
* @private
|
|
1007
|
+
* @param agentId - The parent agent ID
|
|
1008
|
+
* @throws {Error} If any step save fails
|
|
1009
|
+
*/
|
|
1010
|
+
async saveSteps(agentId) {
|
|
1011
|
+
console.log(`🔷 saveSteps: Called with agentId=${agentId}, Steps=${this.spec.Steps ? this.spec.Steps.length : 'undefined'}`);
|
|
1012
|
+
if (!this.spec.Steps || this.spec.Steps.length === 0) {
|
|
1013
|
+
console.log('🔷 saveSteps: No steps to save, returning early');
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
console.log(`🔷 saveSteps: Processing ${this.spec.Steps.length} step(s)...`);
|
|
1017
|
+
const md = new core_1.Metadata();
|
|
1018
|
+
for (const stepSpec of this.spec.Steps) {
|
|
1019
|
+
const stepEntity = await md.GetEntityObject('MJ: AI Agent Steps', this._contextUser);
|
|
1020
|
+
// Load existing if ID provided
|
|
1021
|
+
const isUpdate = !!(stepSpec.ID && stepSpec.ID !== '');
|
|
1022
|
+
if (isUpdate) {
|
|
1023
|
+
await stepEntity.Load(stepSpec.ID);
|
|
1024
|
+
}
|
|
1025
|
+
// Map fields
|
|
1026
|
+
stepEntity.AgentID = agentId;
|
|
1027
|
+
stepEntity.Name = stepSpec.Name;
|
|
1028
|
+
stepEntity.Description = stepSpec.Description || null;
|
|
1029
|
+
stepEntity.StepType = stepSpec.StepType;
|
|
1030
|
+
stepEntity.StartingStep = stepSpec.StartingStep;
|
|
1031
|
+
stepEntity.ActionID = stepSpec.ActionID || null;
|
|
1032
|
+
stepEntity.SubAgentID = stepSpec.SubAgentID || null;
|
|
1033
|
+
stepEntity.Status = 'Active'; // Default to Active
|
|
1034
|
+
// Handle inline prompt creation for Prompt-type steps
|
|
1035
|
+
// If StepType is Prompt and PromptID is empty, create a new AIPrompt record
|
|
1036
|
+
if (stepSpec.StepType === 'Prompt' && (!stepSpec.PromptID || stepSpec.PromptID === '')) {
|
|
1037
|
+
if (stepSpec.PromptText) {
|
|
1038
|
+
console.log(`🔷 saveSteps: Creating inline prompt for step "${stepSpec.Name}"`);
|
|
1039
|
+
// Create AIPrompt entity (using any type for TemplateText dynamic property)
|
|
1040
|
+
const promptEntity = await md.GetEntityObject('AI Prompts', this._contextUser);
|
|
1041
|
+
// Set prompt fields
|
|
1042
|
+
promptEntity.Name = stepSpec.PromptName || `${stepSpec.Name} Prompt`;
|
|
1043
|
+
promptEntity.Description = stepSpec.PromptDescription || `Prompt for step: ${stepSpec.Name}`;
|
|
1044
|
+
promptEntity.TypeID = 'a6da423e-f36b-1410-8dac-00021f8b792e'; // Chat type
|
|
1045
|
+
promptEntity.Status = 'Active';
|
|
1046
|
+
promptEntity.ResponseFormat = 'JSON';
|
|
1047
|
+
promptEntity.TemplateText = stepSpec.PromptText; // TemplateText is a dynamic property
|
|
1048
|
+
const promptSaved = await promptEntity.Save();
|
|
1049
|
+
if (!promptSaved) {
|
|
1050
|
+
throw new Error(`Failed to save inline prompt for step "${stepSpec.Name}"`);
|
|
1051
|
+
}
|
|
1052
|
+
// Track mutation
|
|
1053
|
+
this.trackMutation('AI Prompts', 'Create', promptEntity.ID, `Created inline prompt: ${promptEntity.Name}`);
|
|
1054
|
+
console.log(`✅ saveSteps: Created inline AIPrompt with ID: ${promptEntity.ID}`);
|
|
1055
|
+
// Link the created prompt to this step
|
|
1056
|
+
stepEntity.PromptID = promptEntity.ID;
|
|
1057
|
+
stepSpec.PromptID = promptEntity.ID; // Update spec too
|
|
1058
|
+
}
|
|
1059
|
+
else {
|
|
1060
|
+
console.warn(`⚠️ saveSteps: Step "${stepSpec.Name}" is Prompt type with empty PromptID but no PromptText provided`);
|
|
1061
|
+
stepEntity.PromptID = null;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
stepEntity.PromptID = stepSpec.PromptID || null;
|
|
1066
|
+
}
|
|
1067
|
+
// Map Action I/O mappings for Flow agent action steps
|
|
1068
|
+
// Handle ActionInputMapping - convert object to JSON string if needed
|
|
1069
|
+
if (stepSpec.ActionInputMapping) {
|
|
1070
|
+
stepEntity.ActionInputMapping = typeof stepSpec.ActionInputMapping === 'string'
|
|
1071
|
+
? stepSpec.ActionInputMapping
|
|
1072
|
+
: JSON.stringify(stepSpec.ActionInputMapping);
|
|
1073
|
+
}
|
|
1074
|
+
else {
|
|
1075
|
+
stepEntity.ActionInputMapping = null;
|
|
1076
|
+
}
|
|
1077
|
+
// Handle ActionOutputMapping - convert object to JSON string if needed
|
|
1078
|
+
if (stepSpec.ActionOutputMapping) {
|
|
1079
|
+
stepEntity.ActionOutputMapping = typeof stepSpec.ActionOutputMapping === 'string'
|
|
1080
|
+
? stepSpec.ActionOutputMapping
|
|
1081
|
+
: JSON.stringify(stepSpec.ActionOutputMapping);
|
|
1082
|
+
}
|
|
1083
|
+
else {
|
|
1084
|
+
stepEntity.ActionOutputMapping = null;
|
|
1085
|
+
}
|
|
1086
|
+
const saved = await stepEntity.Save();
|
|
1087
|
+
if (!saved) {
|
|
1088
|
+
throw new Error(`Failed to save step "${stepSpec.Name}" for agent ${this.spec.Name}`);
|
|
1089
|
+
}
|
|
1090
|
+
// Track mutation
|
|
1091
|
+
this.trackMutation('AI Agent Steps', isUpdate ? 'Update' : 'Create', stepEntity.ID, `${isUpdate ? 'Updated' : 'Created'} step: ${stepSpec.Name}`);
|
|
1092
|
+
// Update the spec with the saved ID so paths can reference it
|
|
1093
|
+
stepSpec.ID = stepEntity.ID;
|
|
1094
|
+
console.log(`✅ saveSteps: Created AIAgentStep with ID: ${stepEntity.ID}`);
|
|
1095
|
+
}
|
|
1096
|
+
console.log(`✅ saveSteps: Successfully saved all ${this.spec.Steps.length} step(s)`);
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Save all step paths for this agent (Flow agents only).
|
|
1100
|
+
*
|
|
1101
|
+
* Creates AIAgentStepPath records that define the edges in a Flow agent's
|
|
1102
|
+
* execution graph, including conditional branching logic.
|
|
1103
|
+
*
|
|
1104
|
+
* @private
|
|
1105
|
+
* @param agentId - The parent agent ID
|
|
1106
|
+
* @throws {Error} If any path save fails
|
|
1107
|
+
*/
|
|
1108
|
+
async saveStepPaths(agentId) {
|
|
1109
|
+
console.log(`🔶 saveStepPaths: Called with agentId=${agentId}, Paths=${this.spec.Paths ? this.spec.Paths.length : 'undefined'}`);
|
|
1110
|
+
if (!this.spec.Paths || this.spec.Paths.length === 0) {
|
|
1111
|
+
console.log('🔶 saveStepPaths: No paths to save, returning early');
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
console.log(`🔶 saveStepPaths: Processing ${this.spec.Paths.length} path(s)...`);
|
|
1115
|
+
const md = new core_1.Metadata();
|
|
1116
|
+
// Create a map of step names to IDs for easy lookup
|
|
1117
|
+
const stepNameToId = new Map();
|
|
1118
|
+
if (this.spec.Steps) {
|
|
1119
|
+
for (const step of this.spec.Steps) {
|
|
1120
|
+
if (step.ID) {
|
|
1121
|
+
stepNameToId.set(step.Name, step.ID);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
for (const pathSpec of this.spec.Paths) {
|
|
1126
|
+
const pathEntity = await md.GetEntityObject('MJ: AI Agent Step Paths', this._contextUser);
|
|
1127
|
+
// Load existing if ID provided
|
|
1128
|
+
const isUpdate = !!(pathSpec.ID && pathSpec.ID !== '');
|
|
1129
|
+
if (isUpdate) {
|
|
1130
|
+
await pathEntity.Load(pathSpec.ID);
|
|
1131
|
+
}
|
|
1132
|
+
// Resolve step IDs from step names
|
|
1133
|
+
// OriginStepID and DestinationStepID can be either step IDs or step names
|
|
1134
|
+
// If they're names, look them up in our map
|
|
1135
|
+
let originStepId = pathSpec.OriginStepID;
|
|
1136
|
+
let destinationStepId = pathSpec.DestinationStepID;
|
|
1137
|
+
// Try to resolve as step names first
|
|
1138
|
+
if (stepNameToId.has(pathSpec.OriginStepID)) {
|
|
1139
|
+
originStepId = stepNameToId.get(pathSpec.OriginStepID);
|
|
1140
|
+
}
|
|
1141
|
+
if (stepNameToId.has(pathSpec.DestinationStepID)) {
|
|
1142
|
+
destinationStepId = stepNameToId.get(pathSpec.DestinationStepID);
|
|
1143
|
+
}
|
|
1144
|
+
// Map fields
|
|
1145
|
+
pathEntity.OriginStepID = originStepId;
|
|
1146
|
+
pathEntity.DestinationStepID = destinationStepId;
|
|
1147
|
+
pathEntity.Condition = pathSpec.Condition || null;
|
|
1148
|
+
pathEntity.Description = pathSpec.Description || null;
|
|
1149
|
+
pathEntity.Priority = pathSpec.Priority;
|
|
1150
|
+
const saved = await pathEntity.Save();
|
|
1151
|
+
if (!saved) {
|
|
1152
|
+
throw new Error(`Failed to save path from "${pathSpec.OriginStepID}" to "${pathSpec.DestinationStepID}"`);
|
|
1153
|
+
}
|
|
1154
|
+
// Track mutation
|
|
1155
|
+
this.trackMutation('AI Agent Step Paths', isUpdate ? 'Update' : 'Create', pathEntity.ID, `${isUpdate ? 'Updated' : 'Created'} path from "${pathSpec.OriginStepID}" to "${pathSpec.DestinationStepID}"`);
|
|
1156
|
+
// Update the spec with the saved ID
|
|
1157
|
+
pathSpec.ID = pathEntity.ID;
|
|
1158
|
+
console.log(`✅ saveStepPaths: Created AIAgentStepPath with ID: ${pathEntity.ID}`);
|
|
1159
|
+
}
|
|
1160
|
+
console.log(`✅ saveStepPaths: Successfully saved all ${this.spec.Paths.length} path(s)`);
|
|
1161
|
+
}
|
|
622
1162
|
// ===== UTILITY METHODS =====
|
|
623
1163
|
/**
|
|
624
1164
|
* Parse a JSON string field, returning undefined if null/empty.
|
|
@@ -656,6 +1196,9 @@ class AgentSpecSync {
|
|
|
656
1196
|
ID: partial.ID || '',
|
|
657
1197
|
Name: partial.Name || '',
|
|
658
1198
|
Description: partial.Description,
|
|
1199
|
+
// TypeID and Status are extended fields (not in base AgentSpec interface)
|
|
1200
|
+
TypeID: partial.TypeID,
|
|
1201
|
+
Status: partial.Status || 'Active',
|
|
659
1202
|
IconClass: partial.IconClass,
|
|
660
1203
|
LogoURL: partial.LogoURL,
|
|
661
1204
|
ParentID: partial.ParentID,
|
|
@@ -676,14 +1219,20 @@ class AgentSpecSync {
|
|
|
676
1219
|
MinExecutionsPerRun: partial.MinExecutionsPerRun,
|
|
677
1220
|
MaxExecutionsPerRun: partial.MaxExecutionsPerRun,
|
|
678
1221
|
StartingPayloadValidation: partial.StartingPayloadValidation,
|
|
679
|
-
StartingPayloadValidationMode: partial.StartingPayloadValidationMode ||
|
|
1222
|
+
StartingPayloadValidationMode: partial.StartingPayloadValidationMode || 'Fail',
|
|
680
1223
|
DefaultPromptEffortLevel: partial.DefaultPromptEffortLevel,
|
|
681
1224
|
ChatHandlingOption: partial.ChatHandlingOption,
|
|
682
1225
|
DefaultArtifactTypeID: partial.DefaultArtifactTypeID,
|
|
683
1226
|
OwnerUserID: partial.OwnerUserID,
|
|
684
1227
|
InvocationMode: partial.InvocationMode || 'Any',
|
|
1228
|
+
FunctionalRequirements: partial.FunctionalRequirements,
|
|
1229
|
+
TechnicalDesign: partial.TechnicalDesign,
|
|
685
1230
|
Actions: partial.Actions || [],
|
|
686
|
-
SubAgents: partial.SubAgents || []
|
|
1231
|
+
SubAgents: partial.SubAgents || [],
|
|
1232
|
+
Prompts: partial.Prompts || [],
|
|
1233
|
+
// Flow agent fields - critical for Flow agent support
|
|
1234
|
+
Steps: partial.Steps || [],
|
|
1235
|
+
Paths: partial.Paths || []
|
|
687
1236
|
};
|
|
688
1237
|
}
|
|
689
1238
|
/**
|