@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/src/agent-spec-sync.ts
CHANGED
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
AIAgentEntity,
|
|
9
9
|
AIAgentActionEntity,
|
|
10
|
-
AIAgentRelationshipEntity
|
|
10
|
+
AIAgentRelationshipEntity,
|
|
11
|
+
AIAgentStepEntity,
|
|
12
|
+
AIAgentStepPathEntity
|
|
11
13
|
} from '@memberjunction/core-entities';
|
|
12
14
|
import {
|
|
13
15
|
AgentSpec,
|
|
@@ -15,6 +17,32 @@ import {
|
|
|
15
17
|
SubAgentSpec
|
|
16
18
|
} from '@memberjunction/ai-core-plus';
|
|
17
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Represents a single database mutation performed by AgentSpecSync
|
|
22
|
+
*/
|
|
23
|
+
export interface AgentSpecSyncMutation {
|
|
24
|
+
/** Entity name (e.g., "AI Agents", "AI Prompts", "AIAgentAction") */
|
|
25
|
+
Entity: string;
|
|
26
|
+
/** Operation type */
|
|
27
|
+
Operation: 'Create' | 'Update' | 'Delete';
|
|
28
|
+
/** Record ID */
|
|
29
|
+
ID: string;
|
|
30
|
+
/** Human-readable description of what was done */
|
|
31
|
+
Description: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Result of SaveToDatabase operation including all mutations performed
|
|
36
|
+
*/
|
|
37
|
+
export interface AgentSpecSyncResult {
|
|
38
|
+
/** ID of the saved agent */
|
|
39
|
+
agentId: string;
|
|
40
|
+
/** Whether the operation succeeded */
|
|
41
|
+
success: boolean;
|
|
42
|
+
/** Array of all database mutations performed */
|
|
43
|
+
mutations: AgentSpecSyncMutation[];
|
|
44
|
+
}
|
|
45
|
+
|
|
18
46
|
/**
|
|
19
47
|
* AgentSpecSync provides a high-level interface for working with AI Agent metadata in MemberJunction.
|
|
20
48
|
*
|
|
@@ -89,6 +117,12 @@ export class AgentSpecSync {
|
|
|
89
117
|
*/
|
|
90
118
|
private _contextUser?: UserInfo;
|
|
91
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Tracks all database mutations performed during save operations
|
|
122
|
+
* @private
|
|
123
|
+
*/
|
|
124
|
+
private _mutations: AgentSpecSyncMutation[] = [];
|
|
125
|
+
|
|
92
126
|
/**
|
|
93
127
|
* Create a new AgentSpecSync instance.
|
|
94
128
|
*
|
|
@@ -109,7 +143,7 @@ export class AgentSpecSync {
|
|
|
109
143
|
this.spec = {
|
|
110
144
|
ID: '', // Will be set on save if empty
|
|
111
145
|
Name: '',
|
|
112
|
-
StartingPayloadValidationMode:
|
|
146
|
+
StartingPayloadValidationMode: 'Fail',
|
|
113
147
|
Actions: [],
|
|
114
148
|
SubAgents: []
|
|
115
149
|
};
|
|
@@ -117,6 +151,41 @@ export class AgentSpecSync {
|
|
|
117
151
|
this._contextUser = contextUser;
|
|
118
152
|
}
|
|
119
153
|
|
|
154
|
+
// ===== MUTATION TRACKING METHODS =====
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Track a database mutation performed during save operation
|
|
158
|
+
* @private
|
|
159
|
+
*/
|
|
160
|
+
private trackMutation(
|
|
161
|
+
entity: string,
|
|
162
|
+
operation: 'Create' | 'Update' | 'Delete',
|
|
163
|
+
id: string,
|
|
164
|
+
description: string
|
|
165
|
+
): void {
|
|
166
|
+
this._mutations.push({
|
|
167
|
+
Entity: entity,
|
|
168
|
+
Operation: operation,
|
|
169
|
+
ID: id,
|
|
170
|
+
Description: description
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get all mutations tracked during the last save operation
|
|
176
|
+
* @returns Array of mutations
|
|
177
|
+
*/
|
|
178
|
+
public getMutations(): AgentSpecSyncMutation[] {
|
|
179
|
+
return [...this._mutations];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Clear all tracked mutations
|
|
184
|
+
*/
|
|
185
|
+
public clearMutations(): void {
|
|
186
|
+
this._mutations = [];
|
|
187
|
+
}
|
|
188
|
+
|
|
120
189
|
// ===== STATIC FACTORY METHODS =====
|
|
121
190
|
|
|
122
191
|
/**
|
|
@@ -256,7 +325,8 @@ export class AgentSpecSync {
|
|
|
256
325
|
}
|
|
257
326
|
|
|
258
327
|
// Step 2: Batch load related entities using RunViews for optimal performance
|
|
259
|
-
|
|
328
|
+
// Note: Load paths separately after steps to avoid hardcoded view name in subquery
|
|
329
|
+
const [actionsResult, childAgentsResult, relatedAgentsResult, promptsResult, stepsResult] = await rv.RunViews([
|
|
260
330
|
{
|
|
261
331
|
EntityName: 'AI Agent Actions',
|
|
262
332
|
ExtraFilter: `AgentID='${agentId}'`,
|
|
@@ -274,6 +344,18 @@ export class AgentSpecSync {
|
|
|
274
344
|
ExtraFilter: `AgentID='${agentId}' AND Status='Active'`,
|
|
275
345
|
OrderBy: '__mj_CreatedAt',
|
|
276
346
|
ResultType: 'entity_object'
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
EntityName: 'MJ: AI Agent Prompts',
|
|
350
|
+
ExtraFilter: `AgentID='${agentId}'`,
|
|
351
|
+
OrderBy: 'ExecutionOrder',
|
|
352
|
+
ResultType: 'entity_object'
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
EntityName: 'MJ: AI Agent Steps',
|
|
356
|
+
ExtraFilter: `AgentID='${agentId}'`,
|
|
357
|
+
OrderBy: 'Name',
|
|
358
|
+
ResultType: 'entity_object'
|
|
277
359
|
}
|
|
278
360
|
], this._contextUser);
|
|
279
361
|
|
|
@@ -287,20 +369,101 @@ export class AgentSpecSync {
|
|
|
287
369
|
if (!relatedAgentsResult.Success) {
|
|
288
370
|
throw new Error(`Failed to load related agents: ${relatedAgentsResult.ErrorMessage}`);
|
|
289
371
|
}
|
|
372
|
+
if (!promptsResult.Success) {
|
|
373
|
+
throw new Error(`Failed to load agent prompts: ${promptsResult.ErrorMessage}`);
|
|
374
|
+
}
|
|
375
|
+
if (!stepsResult.Success) {
|
|
376
|
+
throw new Error(`Failed to load agent steps: ${stepsResult.ErrorMessage}`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Step 2b: Load paths separately using step IDs to avoid hardcoded view name
|
|
380
|
+
let pathsResult;
|
|
381
|
+
const steps = stepsResult.Results || [];
|
|
382
|
+
if (steps.length > 0) {
|
|
383
|
+
const stepIds = steps.map((s: AIAgentStepEntity) => `'${s.ID}'`).join(',');
|
|
384
|
+
pathsResult = await rv.RunView<AIAgentStepPathEntity>({
|
|
385
|
+
EntityName: 'MJ: AI Agent Step Paths',
|
|
386
|
+
ExtraFilter: `OriginStepID IN (${stepIds})`,
|
|
387
|
+
OrderBy: 'Priority DESC',
|
|
388
|
+
ResultType: 'entity_object'
|
|
389
|
+
}, this._contextUser);
|
|
390
|
+
|
|
391
|
+
if (!pathsResult.Success) {
|
|
392
|
+
throw new Error(`Failed to load step paths: ${pathsResult.ErrorMessage}`);
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
// No steps, so no paths
|
|
396
|
+
pathsResult = {
|
|
397
|
+
Success: true,
|
|
398
|
+
Results: [],
|
|
399
|
+
RowCount: 0
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Step 2c: Load full AI Prompt records to get PromptText, PromptRole, PromptPosition
|
|
404
|
+
let fullPromptsResult;
|
|
405
|
+
const agentPrompts = promptsResult.Results || [];
|
|
406
|
+
if (agentPrompts.length > 0) {
|
|
407
|
+
const promptIds = agentPrompts.map((p: any) => `'${p.PromptID}'`).join(',');
|
|
408
|
+
fullPromptsResult = await rv.RunView({
|
|
409
|
+
EntityName: 'AI Prompts',
|
|
410
|
+
ExtraFilter: `ID IN (${promptIds})`,
|
|
411
|
+
OrderBy: 'Name',
|
|
412
|
+
ResultType: 'entity_object'
|
|
413
|
+
}, this._contextUser);
|
|
414
|
+
|
|
415
|
+
if (!fullPromptsResult.Success) {
|
|
416
|
+
throw new Error(`Failed to load AI prompts: ${fullPromptsResult.ErrorMessage}`);
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
419
|
+
// No prompts
|
|
420
|
+
fullPromptsResult = {
|
|
421
|
+
Success: true,
|
|
422
|
+
Results: [],
|
|
423
|
+
RowCount: 0
|
|
424
|
+
};
|
|
425
|
+
}
|
|
290
426
|
|
|
291
427
|
// Step 3: Map entities to raw spec format
|
|
292
428
|
this.spec = this.mapEntitiesToRawSpec(
|
|
293
429
|
agentEntity,
|
|
294
430
|
actionsResult.Results || [],
|
|
295
431
|
childAgentsResult.Results || [],
|
|
296
|
-
relatedAgentsResult.Results || []
|
|
432
|
+
relatedAgentsResult.Results || [],
|
|
433
|
+
promptsResult.Results || [],
|
|
434
|
+
fullPromptsResult.Results || [],
|
|
435
|
+
stepsResult.Results || [],
|
|
436
|
+
pathsResult.Results || []
|
|
297
437
|
);
|
|
298
438
|
|
|
299
439
|
// Step 4: Recursively load sub-agents if requested
|
|
300
440
|
if (includeSubAgents && this.spec.SubAgents && this.spec.SubAgents.length > 0) {
|
|
301
|
-
//
|
|
302
|
-
|
|
303
|
-
|
|
441
|
+
// Recursively load complete specs for all sub-agents
|
|
442
|
+
for (const subAgentSpec of this.spec.SubAgents) {
|
|
443
|
+
if (subAgentSpec.SubAgent && subAgentSpec.SubAgent.ID) {
|
|
444
|
+
try {
|
|
445
|
+
// Load the complete sub-agent spec recursively
|
|
446
|
+
const subAgentSync = await AgentSpecSync.LoadFromDatabase(
|
|
447
|
+
subAgentSpec.SubAgent.ID,
|
|
448
|
+
this._contextUser,
|
|
449
|
+
true // Recursively load nested sub-agents too
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
// Replace the minimal SubAgent with the complete spec
|
|
453
|
+
const fullSubAgentSpec = subAgentSync.toJSON();
|
|
454
|
+
|
|
455
|
+
// Preserve the relationship metadata while adding full spec details
|
|
456
|
+
subAgentSpec.SubAgent = {
|
|
457
|
+
...fullSubAgentSpec,
|
|
458
|
+
// Ensure we preserve any relationship-specific overrides
|
|
459
|
+
StartingPayloadValidationMode: subAgentSpec.SubAgent.StartingPayloadValidationMode || fullSubAgentSpec.StartingPayloadValidationMode
|
|
460
|
+
};
|
|
461
|
+
} catch (error) {
|
|
462
|
+
LogError(`Failed to load sub-agent ${subAgentSpec.SubAgent.ID}: ${error instanceof Error ? error.message : String(error)}`);
|
|
463
|
+
// Continue with partial data rather than failing completely
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
304
467
|
}
|
|
305
468
|
|
|
306
469
|
this._isLoaded = true;
|
|
@@ -318,19 +481,29 @@ export class AgentSpecSync {
|
|
|
318
481
|
* @param actions - Array of agent action entities
|
|
319
482
|
* @param childAgents - Array of child agent entities (ParentID-based)
|
|
320
483
|
* @param relatedAgents - Array of related agent relationship entities
|
|
484
|
+
* @param agentPrompts - Array of agent prompt junction entities
|
|
485
|
+
* @param fullPrompts - Array of full AI Prompt entities with PromptText, PromptRole, PromptPosition
|
|
486
|
+
* @param steps - Array of agent step entities (for Flow agents)
|
|
487
|
+
* @param paths - Array of step path entities (for Flow agents)
|
|
321
488
|
* @returns Fully populated AgentSpec object
|
|
322
489
|
*/
|
|
323
490
|
private mapEntitiesToRawSpec(
|
|
324
491
|
agent: AIAgentEntity,
|
|
325
492
|
actions: AIAgentActionEntity[],
|
|
326
493
|
childAgents: AIAgentEntity[],
|
|
327
|
-
relatedAgents: AIAgentRelationshipEntity[]
|
|
494
|
+
relatedAgents: AIAgentRelationshipEntity[],
|
|
495
|
+
agentPrompts: any[],
|
|
496
|
+
fullPrompts: any[],
|
|
497
|
+
steps: AIAgentStepEntity[],
|
|
498
|
+
paths: AIAgentStepPathEntity[]
|
|
328
499
|
): AgentSpec {
|
|
329
500
|
// Map all agent fields to spec
|
|
330
501
|
const spec: AgentSpec = {
|
|
331
502
|
ID: agent.ID,
|
|
332
503
|
Name: agent.Name || '',
|
|
333
504
|
Description: agent.Description || undefined,
|
|
505
|
+
TypeID: agent.TypeID || undefined,
|
|
506
|
+
Status: (agent.Status as 'Active' | 'Inactive' | 'Pending') || undefined,
|
|
334
507
|
IconClass: agent.IconClass || undefined,
|
|
335
508
|
LogoURL: agent.LogoURL || undefined,
|
|
336
509
|
ParentID: agent.ParentID || undefined,
|
|
@@ -369,6 +542,10 @@ export class AgentSpecSync {
|
|
|
369
542
|
OwnerUserID: agent.OwnerUserID || undefined,
|
|
370
543
|
InvocationMode: agent.InvocationMode,
|
|
371
544
|
|
|
545
|
+
// Requirements and design documentation
|
|
546
|
+
FunctionalRequirements: (agent as any).FunctionalRequirements || null,
|
|
547
|
+
TechnicalDesign: (agent as any).TechnicalDesign || null,
|
|
548
|
+
|
|
372
549
|
// Map actions
|
|
373
550
|
Actions: actions.map(action => this.mapActionEntityToSpec(action)),
|
|
374
551
|
|
|
@@ -376,7 +553,16 @@ export class AgentSpecSync {
|
|
|
376
553
|
SubAgents: [
|
|
377
554
|
...childAgents.map(child => this.mapChildAgentToSpec(child)),
|
|
378
555
|
...relatedAgents.map(rel => this.mapRelatedAgentToSpec(rel))
|
|
379
|
-
]
|
|
556
|
+
],
|
|
557
|
+
|
|
558
|
+
// Map prompts (agent-level prompts for Loop agents)
|
|
559
|
+
Prompts: agentPrompts.map(agentPrompt => this.mapPromptEntityToSpec(agentPrompt, fullPrompts)),
|
|
560
|
+
|
|
561
|
+
// Map steps (for Flow agents)
|
|
562
|
+
Steps: steps.map(step => this.mapStepEntityToSpec(step)),
|
|
563
|
+
|
|
564
|
+
// Map paths (for Flow agents)
|
|
565
|
+
Paths: paths.map(path => this.mapPathEntityToSpec(path))
|
|
380
566
|
};
|
|
381
567
|
|
|
382
568
|
return spec;
|
|
@@ -413,7 +599,11 @@ export class AgentSpecSync {
|
|
|
413
599
|
private mapChildAgentToSpec(childAgent: AIAgentEntity): SubAgentSpec {
|
|
414
600
|
return {
|
|
415
601
|
Type: 'child',
|
|
416
|
-
|
|
602
|
+
SubAgent: {
|
|
603
|
+
ID: childAgent.ID,
|
|
604
|
+
Name: childAgent.Name || '',
|
|
605
|
+
StartingPayloadValidationMode: 'Fail'
|
|
606
|
+
}
|
|
417
607
|
};
|
|
418
608
|
}
|
|
419
609
|
|
|
@@ -427,7 +617,11 @@ export class AgentSpecSync {
|
|
|
427
617
|
private mapRelatedAgentToSpec(relationship: AIAgentRelationshipEntity): SubAgentSpec {
|
|
428
618
|
return {
|
|
429
619
|
Type: 'related',
|
|
430
|
-
|
|
620
|
+
SubAgent: {
|
|
621
|
+
ID: relationship.SubAgentID,
|
|
622
|
+
Name: relationship.SubAgent || '',
|
|
623
|
+
StartingPayloadValidationMode: 'Fail'
|
|
624
|
+
},
|
|
431
625
|
AgentRelationshipID: relationship.ID,
|
|
432
626
|
SubAgentInputMapping: this.parseJsonField<Record<string, string>>(relationship.SubAgentInputMapping),
|
|
433
627
|
SubAgentOutputMapping: this.parseJsonField<Record<string, string>>(relationship.SubAgentOutputMapping),
|
|
@@ -435,6 +629,76 @@ export class AgentSpecSync {
|
|
|
435
629
|
};
|
|
436
630
|
}
|
|
437
631
|
|
|
632
|
+
/**
|
|
633
|
+
* Map AIAgentPromptEntity junction to AgentPromptSpec format.
|
|
634
|
+
*
|
|
635
|
+
* @private
|
|
636
|
+
* @param agentPrompt - The agent prompt junction entity (has PromptID, ExecutionOrder)
|
|
637
|
+
* @param fullPrompts - Array of full AI Prompt entities
|
|
638
|
+
* @returns Mapped prompt spec
|
|
639
|
+
*/
|
|
640
|
+
private mapPromptEntityToSpec(agentPrompt: any, fullPrompts: any[]): any {
|
|
641
|
+
// Find the full prompt data by matching PromptID
|
|
642
|
+
const fullPrompt = fullPrompts.find((p: any) => p.ID === agentPrompt.PromptID);
|
|
643
|
+
|
|
644
|
+
if (!fullPrompt) {
|
|
645
|
+
// Fallback if prompt not found
|
|
646
|
+
return {
|
|
647
|
+
PromptID: agentPrompt.PromptID || '',
|
|
648
|
+
PromptText: '',
|
|
649
|
+
PromptRole: 'System',
|
|
650
|
+
PromptPosition: 'First'
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return {
|
|
655
|
+
PromptID: fullPrompt.ID || '',
|
|
656
|
+
PromptText: fullPrompt.TemplateText || '',
|
|
657
|
+
PromptRole: fullPrompt.PromptRole || 'System',
|
|
658
|
+
PromptPosition: fullPrompt.PromptPosition || 'First'
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Map AIAgentStepEntity to AgentStep format.
|
|
664
|
+
*
|
|
665
|
+
* @private
|
|
666
|
+
* @param step - The agent step entity
|
|
667
|
+
* @returns Mapped step spec
|
|
668
|
+
*/
|
|
669
|
+
private mapStepEntityToSpec(step: AIAgentStepEntity): any {
|
|
670
|
+
return {
|
|
671
|
+
ID: step.ID,
|
|
672
|
+
Name: step.Name,
|
|
673
|
+
Description: step.Description || undefined,
|
|
674
|
+
StepType: step.StepType,
|
|
675
|
+
StartingStep: step.StartingStep,
|
|
676
|
+
ActionID: step.ActionID || undefined,
|
|
677
|
+
SubAgentID: step.SubAgentID || undefined,
|
|
678
|
+
PromptID: step.PromptID || undefined,
|
|
679
|
+
ActionInputMapping: this.parseJsonField<any>(step.ActionInputMapping),
|
|
680
|
+
ActionOutputMapping: this.parseJsonField<any>(step.ActionOutputMapping)
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Map AIAgentStepPathEntity to AgentStepPath format.
|
|
686
|
+
*
|
|
687
|
+
* @private
|
|
688
|
+
* @param path - The agent step path entity
|
|
689
|
+
* @returns Mapped path spec
|
|
690
|
+
*/
|
|
691
|
+
private mapPathEntityToSpec(path: AIAgentStepPathEntity): any {
|
|
692
|
+
return {
|
|
693
|
+
ID: path.ID,
|
|
694
|
+
OriginStepID: path.OriginStepID,
|
|
695
|
+
DestinationStepID: path.DestinationStepID,
|
|
696
|
+
Condition: path.Condition || undefined,
|
|
697
|
+
Description: path.Description || undefined,
|
|
698
|
+
Priority: path.Priority
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
|
|
438
702
|
// ===== SAVING METHODS =====
|
|
439
703
|
|
|
440
704
|
/**
|
|
@@ -447,7 +711,7 @@ export class AgentSpecSync {
|
|
|
447
711
|
* database schema are executed, and any failures will prevent the save.
|
|
448
712
|
*
|
|
449
713
|
* @param validate - Whether to validate before saving (default: true)
|
|
450
|
-
* @returns Promise resolving to
|
|
714
|
+
* @returns Promise resolving to result with agent ID, success flag, and all mutations performed
|
|
451
715
|
* @throws {Error} If validation fails or save operation fails
|
|
452
716
|
*
|
|
453
717
|
* @example
|
|
@@ -457,29 +721,59 @@ export class AgentSpecSync {
|
|
|
457
721
|
* InvocationMode: 'Any'
|
|
458
722
|
* }, contextUser);
|
|
459
723
|
*
|
|
460
|
-
* const
|
|
461
|
-
* console.log('Saved with ID:', agentId);
|
|
724
|
+
* const result = await spec.SaveToDatabase();
|
|
725
|
+
* console.log('Saved with ID:', result.agentId);
|
|
726
|
+
* console.log('Mutations:', result.mutations.length);
|
|
462
727
|
* ```
|
|
463
728
|
*/
|
|
464
|
-
async SaveToDatabase(validate: boolean = true): Promise<
|
|
729
|
+
async SaveToDatabase(validate: boolean = true): Promise<AgentSpecSyncResult> {
|
|
730
|
+
// Clear previous mutations
|
|
731
|
+
this.clearMutations();
|
|
732
|
+
|
|
465
733
|
if (!this._isDirty && this._isLoaded) {
|
|
466
734
|
// No changes to save
|
|
467
|
-
return
|
|
735
|
+
return {
|
|
736
|
+
agentId: this.spec.ID,
|
|
737
|
+
success: true,
|
|
738
|
+
mutations: []
|
|
739
|
+
};
|
|
468
740
|
}
|
|
469
741
|
|
|
470
|
-
|
|
471
|
-
|
|
742
|
+
try {
|
|
743
|
+
// Step 1: Save main agent entity
|
|
744
|
+
const agentId = await this.saveAgentEntity(validate);
|
|
472
745
|
|
|
473
|
-
|
|
474
|
-
|
|
746
|
+
// Step 2: Save actions
|
|
747
|
+
await this.saveActions(agentId);
|
|
475
748
|
|
|
476
|
-
|
|
477
|
-
|
|
749
|
+
// Step 3: Save sub-agents (both child and related)
|
|
750
|
+
await this.saveSubAgents(agentId);
|
|
478
751
|
|
|
479
|
-
|
|
480
|
-
|
|
752
|
+
// Step 4: Save prompts
|
|
753
|
+
await this.savePrompts(agentId);
|
|
754
|
+
|
|
755
|
+
// Step 5: Save steps (Flow agents only)
|
|
756
|
+
await this.saveSteps(agentId);
|
|
757
|
+
|
|
758
|
+
// Step 6: Save step paths (Flow agents only)
|
|
759
|
+
await this.saveStepPaths(agentId);
|
|
760
|
+
|
|
761
|
+
this._isDirty = false;
|
|
762
|
+
this._isLoaded = true;
|
|
481
763
|
|
|
482
|
-
|
|
764
|
+
return {
|
|
765
|
+
agentId,
|
|
766
|
+
success: true,
|
|
767
|
+
mutations: this.getMutations()
|
|
768
|
+
};
|
|
769
|
+
} catch (error) {
|
|
770
|
+
// Return failure with whatever mutations were completed before error
|
|
771
|
+
return {
|
|
772
|
+
agentId: this.spec.ID || '',
|
|
773
|
+
success: false,
|
|
774
|
+
mutations: this.getMutations()
|
|
775
|
+
};
|
|
776
|
+
}
|
|
483
777
|
}
|
|
484
778
|
|
|
485
779
|
/**
|
|
@@ -497,6 +791,9 @@ export class AgentSpecSync {
|
|
|
497
791
|
this._contextUser
|
|
498
792
|
);
|
|
499
793
|
|
|
794
|
+
// Track if this is an update or create
|
|
795
|
+
const isUpdate = !!this.spec.ID;
|
|
796
|
+
|
|
500
797
|
// If ID exists, load existing record
|
|
501
798
|
if (this.spec.ID) {
|
|
502
799
|
const loaded = await agentEntity.Load(this.spec.ID);
|
|
@@ -514,6 +811,18 @@ export class AgentSpecSync {
|
|
|
514
811
|
agentEntity.DriverClass = this.spec.DriverClass || null;
|
|
515
812
|
agentEntity.ModelSelectionMode = this.spec.ModelSelectionMode || 'Agent Type';
|
|
516
813
|
|
|
814
|
+
// Handle TypeID - supports lookup references or direct GUID
|
|
815
|
+
if ((this.spec as any).TypeID) {
|
|
816
|
+
agentEntity.TypeID = (this.spec as any).TypeID;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Handle Status - defaults to Active if not specified
|
|
820
|
+
if ((this.spec as any).Status) {
|
|
821
|
+
agentEntity.Status = (this.spec as any).Status;
|
|
822
|
+
} else {
|
|
823
|
+
agentEntity.Status = 'Active';
|
|
824
|
+
}
|
|
825
|
+
|
|
517
826
|
// Serialize JSON fields
|
|
518
827
|
agentEntity.PayloadDownstreamPaths = JSON.stringify(
|
|
519
828
|
this.spec.PayloadDownstreamPaths || ['*']
|
|
@@ -535,7 +844,7 @@ export class AgentSpecSync {
|
|
|
535
844
|
agentEntity.FinalPayloadValidationMaxRetries = this.spec.FinalPayloadValidationMaxRetries || 3;
|
|
536
845
|
|
|
537
846
|
agentEntity.StartingPayloadValidation = this.spec.StartingPayloadValidation || null;
|
|
538
|
-
agentEntity.StartingPayloadValidationMode = this.spec.StartingPayloadValidationMode
|
|
847
|
+
agentEntity.StartingPayloadValidationMode = this.spec.StartingPayloadValidationMode || 'Fail';
|
|
539
848
|
|
|
540
849
|
// Resource limits
|
|
541
850
|
agentEntity.MaxCostPerRun = this.spec.MaxCostPerRun || null;
|
|
@@ -556,6 +865,10 @@ export class AgentSpecSync {
|
|
|
556
865
|
}
|
|
557
866
|
agentEntity.InvocationMode = this.spec.InvocationMode || 'Any';
|
|
558
867
|
|
|
868
|
+
// Requirements and design documentation
|
|
869
|
+
agentEntity.FunctionalRequirements = this.spec.FunctionalRequirements || null;
|
|
870
|
+
agentEntity.TechnicalDesign = this.spec.TechnicalDesign || null;
|
|
871
|
+
|
|
559
872
|
// Validate if requested
|
|
560
873
|
if (validate) {
|
|
561
874
|
const validation = agentEntity.Validate();
|
|
@@ -573,6 +886,15 @@ export class AgentSpecSync {
|
|
|
573
886
|
|
|
574
887
|
// Update spec with saved ID
|
|
575
888
|
this.spec.ID = agentEntity.ID;
|
|
889
|
+
|
|
890
|
+
// Track the mutation
|
|
891
|
+
this.trackMutation(
|
|
892
|
+
'AI Agents',
|
|
893
|
+
isUpdate ? 'Update' : 'Create',
|
|
894
|
+
agentEntity.ID,
|
|
895
|
+
`${isUpdate ? 'Updated' : 'Created'} agent: ${this.spec.Name}`
|
|
896
|
+
);
|
|
897
|
+
|
|
576
898
|
return agentEntity.ID;
|
|
577
899
|
}
|
|
578
900
|
|
|
@@ -600,6 +922,9 @@ export class AgentSpecSync {
|
|
|
600
922
|
this._contextUser
|
|
601
923
|
);
|
|
602
924
|
|
|
925
|
+
// Track if this is an update or create
|
|
926
|
+
const isUpdate = !!actionSpec.AgentActionID;
|
|
927
|
+
|
|
603
928
|
// Load existing if ID provided
|
|
604
929
|
if (actionSpec.AgentActionID) {
|
|
605
930
|
await actionEntity.Load(actionSpec.AgentActionID);
|
|
@@ -623,6 +948,14 @@ export class AgentSpecSync {
|
|
|
623
948
|
|
|
624
949
|
// Update spec with saved ID
|
|
625
950
|
actionSpec.AgentActionID = actionEntity.ID;
|
|
951
|
+
|
|
952
|
+
// Track the mutation
|
|
953
|
+
this.trackMutation(
|
|
954
|
+
'AI Agent Actions',
|
|
955
|
+
isUpdate ? 'Update' : 'Create',
|
|
956
|
+
actionEntity.ID,
|
|
957
|
+
`${isUpdate ? 'Updated' : 'Created'} action junction for action ${actionSpec.ActionID}`
|
|
958
|
+
);
|
|
626
959
|
}
|
|
627
960
|
}
|
|
628
961
|
|
|
@@ -655,39 +988,56 @@ export class AgentSpecSync {
|
|
|
655
988
|
* Save a child sub-agent (ParentID-based relationship).
|
|
656
989
|
*
|
|
657
990
|
* For child agents, the relationship is established by setting the ParentID field
|
|
658
|
-
* on the child agent entity. If the
|
|
659
|
-
* child agent
|
|
991
|
+
* on the child agent entity. If the SubAgent.ID is empty, this creates a new
|
|
992
|
+
* child agent recursively using AgentSpecSync.
|
|
993
|
+
*
|
|
994
|
+
* This enables creating complete agent hierarchies in one call - sub-agents are
|
|
995
|
+
* created first (depth-first), then parent references them via ParentID.
|
|
660
996
|
*
|
|
661
997
|
* @private
|
|
662
998
|
* @param parentId - The parent agent ID
|
|
663
999
|
* @param SubAgentSpec - The sub-agent specification
|
|
664
|
-
* @throws {Error} If child agent
|
|
1000
|
+
* @throws {Error} If child agent save fails
|
|
665
1001
|
*/
|
|
666
1002
|
private async saveChildSubAgent(parentId: string, SubAgentSpec: SubAgentSpec): Promise<void> {
|
|
667
|
-
|
|
668
|
-
throw new Error('Child sub-agent must have a SubAgentID');
|
|
669
|
-
}
|
|
1003
|
+
console.log(`🔗 saveChildSubAgent: Processing child "${SubAgentSpec.SubAgent?.Name}", ID="${SubAgentSpec.SubAgent?.ID}"`);
|
|
670
1004
|
|
|
671
|
-
//
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
const childEntity = await md.GetEntityObject<AIAgentEntity>(
|
|
675
|
-
'AI Agents',
|
|
676
|
-
this._contextUser
|
|
677
|
-
);
|
|
1005
|
+
// If SubAgent.ID is empty or missing, create the sub-agent recursively
|
|
1006
|
+
if (!SubAgentSpec.SubAgent?.ID || SubAgentSpec.SubAgent.ID === '') {
|
|
1007
|
+
console.log(`🔨 saveChildSubAgent: Creating new child sub-agent "${SubAgentSpec.SubAgent.Name}"...`);
|
|
678
1008
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
1009
|
+
// Set ParentID in the sub-agent spec before creating it
|
|
1010
|
+
const childSpec: AgentSpec = {
|
|
1011
|
+
...SubAgentSpec.SubAgent,
|
|
1012
|
+
ParentID: parentId
|
|
1013
|
+
};
|
|
683
1014
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
const
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
1015
|
+
// Recursively create the sub-agent using AgentSpecSync
|
|
1016
|
+
const childSync = new AgentSpecSync(childSpec, this._contextUser);
|
|
1017
|
+
childSync.markDirty();
|
|
1018
|
+
const childResult = await childSync.SaveToDatabase();
|
|
1019
|
+
|
|
1020
|
+
// Update the SubAgentSpec with the created ID so parent can reference it
|
|
1021
|
+
SubAgentSpec.SubAgent.ID = childResult.agentId;
|
|
1022
|
+
|
|
1023
|
+
console.log(`✅ saveChildSubAgent: Created child sub-agent with ID: ${childResult.agentId}`);
|
|
1024
|
+
} else {
|
|
1025
|
+
// SubAgent already exists - update it recursively to capture all field changes
|
|
1026
|
+
console.log(`🔗 saveChildSubAgent: Updating existing child sub-agent "${SubAgentSpec.SubAgent.ID}"...`);
|
|
1027
|
+
|
|
1028
|
+
// Set ParentID in the sub-agent spec
|
|
1029
|
+
const childSpec: AgentSpec = {
|
|
1030
|
+
...SubAgentSpec.SubAgent,
|
|
1031
|
+
ParentID: parentId
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
// Recursively update the sub-agent using AgentSpecSync
|
|
1035
|
+
// This ensures all fields including FunctionalRequirements and TechnicalDesign are updated
|
|
1036
|
+
const childSync = new AgentSpecSync(childSpec, this._contextUser);
|
|
1037
|
+
childSync.markDirty();
|
|
1038
|
+
await childSync.SaveToDatabase();
|
|
1039
|
+
|
|
1040
|
+
console.log(`✅ saveChildSubAgent: Updated existing child sub-agent`);
|
|
691
1041
|
}
|
|
692
1042
|
}
|
|
693
1043
|
|
|
@@ -710,13 +1060,14 @@ export class AgentSpecSync {
|
|
|
710
1060
|
);
|
|
711
1061
|
|
|
712
1062
|
// Load existing if ID provided
|
|
713
|
-
|
|
1063
|
+
const isUpdate = !!SubAgentSpec.AgentRelationshipID;
|
|
1064
|
+
if (isUpdate && SubAgentSpec.AgentRelationshipID) {
|
|
714
1065
|
await relationshipEntity.Load(SubAgentSpec.AgentRelationshipID);
|
|
715
1066
|
}
|
|
716
1067
|
|
|
717
1068
|
// Map fields
|
|
718
1069
|
relationshipEntity.AgentID = agentId;
|
|
719
|
-
relationshipEntity.SubAgentID = SubAgentSpec.
|
|
1070
|
+
relationshipEntity.SubAgentID = SubAgentSpec.SubAgent.ID;
|
|
720
1071
|
relationshipEntity.Status = 'Active';
|
|
721
1072
|
|
|
722
1073
|
// Serialize mapping fields
|
|
@@ -740,13 +1091,378 @@ export class AgentSpecSync {
|
|
|
740
1091
|
|
|
741
1092
|
const saved = await relationshipEntity.Save();
|
|
742
1093
|
if (!saved) {
|
|
743
|
-
throw new Error(`Failed to save relationship for sub-agent ${SubAgentSpec.
|
|
1094
|
+
throw new Error(`Failed to save relationship for sub-agent ${SubAgentSpec.SubAgent.ID}`);
|
|
744
1095
|
}
|
|
745
1096
|
|
|
1097
|
+
// Track mutation
|
|
1098
|
+
this.trackMutation(
|
|
1099
|
+
'AI Agent Relationships',
|
|
1100
|
+
isUpdate ? 'Update' : 'Create',
|
|
1101
|
+
relationshipEntity.ID,
|
|
1102
|
+
`${isUpdate ? 'Updated' : 'Created'} agent relationship for sub-agent ${SubAgentSpec.SubAgent.ID}`
|
|
1103
|
+
);
|
|
1104
|
+
|
|
746
1105
|
// Update spec with saved ID
|
|
747
1106
|
SubAgentSpec.AgentRelationshipID = relationshipEntity.ID;
|
|
748
1107
|
}
|
|
749
1108
|
|
|
1109
|
+
/**
|
|
1110
|
+
* Save all prompts for this agent.
|
|
1111
|
+
*
|
|
1112
|
+
* Creates AIPrompt records (with template) and AIAgentPrompt junction records.
|
|
1113
|
+
* Supports simplified prompt format from Architect Agent with just PromptText,
|
|
1114
|
+
* PromptRole, and PromptPosition.
|
|
1115
|
+
*
|
|
1116
|
+
* @private
|
|
1117
|
+
* @param agentId - The parent agent ID
|
|
1118
|
+
* @throws {Error} If any prompt save fails
|
|
1119
|
+
*/
|
|
1120
|
+
private async savePrompts(agentId: string): Promise<void> {
|
|
1121
|
+
console.log(`💬 savePrompts: Called with agentId=${agentId}, Prompts=${this.spec.Prompts ? this.spec.Prompts.length : 'undefined'}`);
|
|
1122
|
+
|
|
1123
|
+
if (!this.spec.Prompts || this.spec.Prompts.length === 0) {
|
|
1124
|
+
console.log('💬 savePrompts: No prompts to save, returning early');
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
console.log(`💬 savePrompts: Processing ${this.spec.Prompts.length} prompt(s)...`);
|
|
1129
|
+
const md = new Metadata();
|
|
1130
|
+
const rv = new RunView();
|
|
1131
|
+
|
|
1132
|
+
// Step 1: Create or update AIPrompt records
|
|
1133
|
+
for (let i = 0; i < this.spec.Prompts.length; i++) {
|
|
1134
|
+
const promptSpec = this.spec.Prompts[i];
|
|
1135
|
+
|
|
1136
|
+
// Check if this is an update (has PromptID) or create (no PromptID)
|
|
1137
|
+
let promptEntity = await md.GetEntityObject<any>(
|
|
1138
|
+
'AI Prompts',
|
|
1139
|
+
this._contextUser
|
|
1140
|
+
);
|
|
1141
|
+
|
|
1142
|
+
let isUpdate = false;
|
|
1143
|
+
if ((promptSpec as any).PromptID && (promptSpec as any).PromptID !== '') {
|
|
1144
|
+
// Load existing prompt for update
|
|
1145
|
+
const loaded = await promptEntity.Load((promptSpec as any).PromptID);
|
|
1146
|
+
if (loaded) {
|
|
1147
|
+
isUpdate = true;
|
|
1148
|
+
console.log(`💬 savePrompts: Updating existing prompt ID: ${(promptSpec as any).PromptID}`);
|
|
1149
|
+
} else {
|
|
1150
|
+
console.warn(`💬 savePrompts: PromptID specified but not found, creating new prompt`);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// Set all required fields (both create and update)
|
|
1155
|
+
promptEntity.Name = `${this.spec.Name} - Prompt ${i + 1}`;
|
|
1156
|
+
promptEntity.Description = `Agent prompt ${i + 1} for ${this.spec.Name}`;
|
|
1157
|
+
promptEntity.TypeID = 'a6da423e-f36b-1410-8dac-00021f8b792e'; // Chat type
|
|
1158
|
+
promptEntity.Status = 'Active';
|
|
1159
|
+
promptEntity.ResponseFormat = 'JSON';
|
|
1160
|
+
|
|
1161
|
+
// Handle prompt text - supports both string and object formats
|
|
1162
|
+
if (typeof (promptSpec as any).PromptText === 'string') {
|
|
1163
|
+
promptEntity.TemplateText = (promptSpec as any).PromptText;
|
|
1164
|
+
} else if (typeof (promptSpec as any).PromptText === 'object') {
|
|
1165
|
+
// Architect may send PromptText as {text: "...", json: {...}}
|
|
1166
|
+
const promptTextObj = (promptSpec as any).PromptText as any;
|
|
1167
|
+
let combinedText = promptTextObj.text || '';
|
|
1168
|
+
if (promptTextObj.json) {
|
|
1169
|
+
combinedText += '\n\n```json\n' + JSON.stringify(promptTextObj.json, null, 2) + '\n```';
|
|
1170
|
+
}
|
|
1171
|
+
promptEntity.TemplateText = combinedText;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// Set prompt role and position if provided
|
|
1175
|
+
if ((promptSpec as any).PromptRole) {
|
|
1176
|
+
promptEntity.PromptRole = (promptSpec as any).PromptRole;
|
|
1177
|
+
}
|
|
1178
|
+
if ((promptSpec as any).PromptPosition) {
|
|
1179
|
+
promptEntity.PromptPosition = (promptSpec as any).PromptPosition;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
const saved = await promptEntity.Save();
|
|
1183
|
+
if (!saved) {
|
|
1184
|
+
throw new Error(`Failed to save prompt ${i + 1} for agent ${this.spec.Name}`);
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// Track mutation
|
|
1188
|
+
this.trackMutation(
|
|
1189
|
+
'AI Prompts',
|
|
1190
|
+
isUpdate ? 'Update' : 'Create',
|
|
1191
|
+
promptEntity.ID,
|
|
1192
|
+
`${isUpdate ? 'Updated' : 'Created'} prompt: ${promptEntity.Name}`
|
|
1193
|
+
);
|
|
1194
|
+
|
|
1195
|
+
// Update the spec with the saved/updated ID
|
|
1196
|
+
(promptSpec as any).PromptID = promptEntity.ID;
|
|
1197
|
+
|
|
1198
|
+
console.log(`✅ savePrompts: ${isUpdate ? 'Updated' : 'Created'} AIPrompt with ID: ${promptEntity.ID}`);
|
|
1199
|
+
|
|
1200
|
+
// Step 2: Create or update AIAgentPrompt junction
|
|
1201
|
+
// Query for existing junction by AgentID + PromptID
|
|
1202
|
+
const existingJunctionResult = await rv.RunView<any>({
|
|
1203
|
+
EntityName: 'MJ: AI Agent Prompts',
|
|
1204
|
+
ExtraFilter: `AgentID='${agentId}' AND PromptID='${promptEntity.ID}'`,
|
|
1205
|
+
ResultType: 'entity_object'
|
|
1206
|
+
}, this._contextUser);
|
|
1207
|
+
|
|
1208
|
+
let agentPromptEntity: any;
|
|
1209
|
+
let isJunctionUpdate = false;
|
|
1210
|
+
|
|
1211
|
+
if (existingJunctionResult.Success && existingJunctionResult.Results && existingJunctionResult.Results.length > 0) {
|
|
1212
|
+
// Junction exists - load it for update
|
|
1213
|
+
agentPromptEntity = existingJunctionResult.Results[0];
|
|
1214
|
+
isJunctionUpdate = true;
|
|
1215
|
+
console.log(`💬 savePrompts: Updating existing junction for prompt ${i + 1}`);
|
|
1216
|
+
} else {
|
|
1217
|
+
// Junction doesn't exist - create new one
|
|
1218
|
+
agentPromptEntity = await md.GetEntityObject<any>(
|
|
1219
|
+
'MJ: AI Agent Prompts',
|
|
1220
|
+
this._contextUser
|
|
1221
|
+
);
|
|
1222
|
+
agentPromptEntity.AgentID = agentId;
|
|
1223
|
+
agentPromptEntity.PromptID = promptEntity.ID;
|
|
1224
|
+
console.log(`💬 savePrompts: Creating new junction for prompt ${i + 1}`);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
// Set/update common fields (ExecutionOrder might change)
|
|
1228
|
+
agentPromptEntity.ExecutionOrder = i;
|
|
1229
|
+
agentPromptEntity.Status = 'Active';
|
|
1230
|
+
|
|
1231
|
+
const junctionSaved = await agentPromptEntity.Save();
|
|
1232
|
+
if (!junctionSaved) {
|
|
1233
|
+
throw new Error(`Failed to save agent-prompt junction for prompt ${i + 1}`);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// Track mutation
|
|
1237
|
+
this.trackMutation(
|
|
1238
|
+
'AI Agent Prompts',
|
|
1239
|
+
isJunctionUpdate ? 'Update' : 'Create',
|
|
1240
|
+
agentPromptEntity.ID,
|
|
1241
|
+
`${isJunctionUpdate ? 'Updated' : 'Created'} agent-prompt junction for prompt ${promptEntity.ID}`
|
|
1242
|
+
);
|
|
1243
|
+
|
|
1244
|
+
console.log(`✅ savePrompts: ${isJunctionUpdate ? 'Updated' : 'Created'} AIAgentPrompt junction with ID: ${agentPromptEntity.ID}`);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
console.log(`✅ savePrompts: Successfully saved all ${this.spec.Prompts.length} prompt(s)`);
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Save all steps for this agent (Flow agents only).
|
|
1252
|
+
*
|
|
1253
|
+
* Creates AIAgentStep records for each step in the spec. Steps define the nodes
|
|
1254
|
+
* in a Flow agent's execution graph.
|
|
1255
|
+
*
|
|
1256
|
+
* @private
|
|
1257
|
+
* @param agentId - The parent agent ID
|
|
1258
|
+
* @throws {Error} If any step save fails
|
|
1259
|
+
*/
|
|
1260
|
+
private async saveSteps(agentId: string): Promise<void> {
|
|
1261
|
+
console.log(`🔷 saveSteps: Called with agentId=${agentId}, Steps=${this.spec.Steps ? this.spec.Steps.length : 'undefined'}`);
|
|
1262
|
+
|
|
1263
|
+
if (!this.spec.Steps || this.spec.Steps.length === 0) {
|
|
1264
|
+
console.log('🔷 saveSteps: No steps to save, returning early');
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
console.log(`🔷 saveSteps: Processing ${this.spec.Steps.length} step(s)...`);
|
|
1269
|
+
const md = new Metadata();
|
|
1270
|
+
|
|
1271
|
+
for (const stepSpec of this.spec.Steps) {
|
|
1272
|
+
const stepEntity = await md.GetEntityObject<AIAgentStepEntity>(
|
|
1273
|
+
'MJ: AI Agent Steps',
|
|
1274
|
+
this._contextUser
|
|
1275
|
+
);
|
|
1276
|
+
|
|
1277
|
+
// Load existing if ID provided
|
|
1278
|
+
const isUpdate = !!(stepSpec.ID && stepSpec.ID !== '');
|
|
1279
|
+
if (isUpdate) {
|
|
1280
|
+
await stepEntity.Load(stepSpec.ID);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// Map fields
|
|
1284
|
+
stepEntity.AgentID = agentId;
|
|
1285
|
+
stepEntity.Name = stepSpec.Name;
|
|
1286
|
+
stepEntity.Description = stepSpec.Description || null;
|
|
1287
|
+
stepEntity.StepType = stepSpec.StepType;
|
|
1288
|
+
stepEntity.StartingStep = stepSpec.StartingStep;
|
|
1289
|
+
stepEntity.ActionID = stepSpec.ActionID || null;
|
|
1290
|
+
stepEntity.SubAgentID = stepSpec.SubAgentID || null;
|
|
1291
|
+
stepEntity.Status = 'Active'; // Default to Active
|
|
1292
|
+
|
|
1293
|
+
// Handle inline prompt creation for Prompt-type steps
|
|
1294
|
+
// If StepType is Prompt and PromptID is empty, create a new AIPrompt record
|
|
1295
|
+
if (stepSpec.StepType === 'Prompt' && (!stepSpec.PromptID || stepSpec.PromptID === '')) {
|
|
1296
|
+
if (stepSpec.PromptText) {
|
|
1297
|
+
console.log(`🔷 saveSteps: Creating inline prompt for step "${stepSpec.Name}"`);
|
|
1298
|
+
|
|
1299
|
+
// Create AIPrompt entity (using any type for TemplateText dynamic property)
|
|
1300
|
+
const promptEntity = await md.GetEntityObject<any>(
|
|
1301
|
+
'AI Prompts',
|
|
1302
|
+
this._contextUser
|
|
1303
|
+
);
|
|
1304
|
+
|
|
1305
|
+
// Set prompt fields
|
|
1306
|
+
promptEntity.Name = stepSpec.PromptName || `${stepSpec.Name} Prompt`;
|
|
1307
|
+
promptEntity.Description = stepSpec.PromptDescription || `Prompt for step: ${stepSpec.Name}`;
|
|
1308
|
+
promptEntity.TypeID = 'a6da423e-f36b-1410-8dac-00021f8b792e'; // Chat type
|
|
1309
|
+
promptEntity.Status = 'Active';
|
|
1310
|
+
promptEntity.ResponseFormat = 'JSON';
|
|
1311
|
+
promptEntity.TemplateText = stepSpec.PromptText; // TemplateText is a dynamic property
|
|
1312
|
+
|
|
1313
|
+
const promptSaved = await promptEntity.Save();
|
|
1314
|
+
if (!promptSaved) {
|
|
1315
|
+
throw new Error(`Failed to save inline prompt for step "${stepSpec.Name}"`);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// Track mutation
|
|
1319
|
+
this.trackMutation(
|
|
1320
|
+
'AI Prompts',
|
|
1321
|
+
'Create',
|
|
1322
|
+
promptEntity.ID,
|
|
1323
|
+
`Created inline prompt: ${promptEntity.Name}`
|
|
1324
|
+
);
|
|
1325
|
+
|
|
1326
|
+
console.log(`✅ saveSteps: Created inline AIPrompt with ID: ${promptEntity.ID}`);
|
|
1327
|
+
|
|
1328
|
+
// Link the created prompt to this step
|
|
1329
|
+
stepEntity.PromptID = promptEntity.ID;
|
|
1330
|
+
stepSpec.PromptID = promptEntity.ID; // Update spec too
|
|
1331
|
+
} else {
|
|
1332
|
+
console.warn(`⚠️ saveSteps: Step "${stepSpec.Name}" is Prompt type with empty PromptID but no PromptText provided`);
|
|
1333
|
+
stepEntity.PromptID = null;
|
|
1334
|
+
}
|
|
1335
|
+
} else {
|
|
1336
|
+
stepEntity.PromptID = stepSpec.PromptID || null;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// Map Action I/O mappings for Flow agent action steps
|
|
1340
|
+
// Handle ActionInputMapping - convert object to JSON string if needed
|
|
1341
|
+
if (stepSpec.ActionInputMapping) {
|
|
1342
|
+
stepEntity.ActionInputMapping = typeof stepSpec.ActionInputMapping === 'string'
|
|
1343
|
+
? stepSpec.ActionInputMapping
|
|
1344
|
+
: JSON.stringify(stepSpec.ActionInputMapping);
|
|
1345
|
+
} else {
|
|
1346
|
+
stepEntity.ActionInputMapping = null;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// Handle ActionOutputMapping - convert object to JSON string if needed
|
|
1350
|
+
if (stepSpec.ActionOutputMapping) {
|
|
1351
|
+
stepEntity.ActionOutputMapping = typeof stepSpec.ActionOutputMapping === 'string'
|
|
1352
|
+
? stepSpec.ActionOutputMapping
|
|
1353
|
+
: JSON.stringify(stepSpec.ActionOutputMapping);
|
|
1354
|
+
} else {
|
|
1355
|
+
stepEntity.ActionOutputMapping = null;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
const saved = await stepEntity.Save();
|
|
1359
|
+
if (!saved) {
|
|
1360
|
+
throw new Error(`Failed to save step "${stepSpec.Name}" for agent ${this.spec.Name}`);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// Track mutation
|
|
1364
|
+
this.trackMutation(
|
|
1365
|
+
'AI Agent Steps',
|
|
1366
|
+
isUpdate ? 'Update' : 'Create',
|
|
1367
|
+
stepEntity.ID,
|
|
1368
|
+
`${isUpdate ? 'Updated' : 'Created'} step: ${stepSpec.Name}`
|
|
1369
|
+
);
|
|
1370
|
+
|
|
1371
|
+
// Update the spec with the saved ID so paths can reference it
|
|
1372
|
+
stepSpec.ID = stepEntity.ID;
|
|
1373
|
+
|
|
1374
|
+
console.log(`✅ saveSteps: Created AIAgentStep with ID: ${stepEntity.ID}`);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
console.log(`✅ saveSteps: Successfully saved all ${this.spec.Steps.length} step(s)`);
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
/**
|
|
1381
|
+
* Save all step paths for this agent (Flow agents only).
|
|
1382
|
+
*
|
|
1383
|
+
* Creates AIAgentStepPath records that define the edges in a Flow agent's
|
|
1384
|
+
* execution graph, including conditional branching logic.
|
|
1385
|
+
*
|
|
1386
|
+
* @private
|
|
1387
|
+
* @param agentId - The parent agent ID
|
|
1388
|
+
* @throws {Error} If any path save fails
|
|
1389
|
+
*/
|
|
1390
|
+
private async saveStepPaths(agentId: string): Promise<void> {
|
|
1391
|
+
console.log(`🔶 saveStepPaths: Called with agentId=${agentId}, Paths=${this.spec.Paths ? this.spec.Paths.length : 'undefined'}`);
|
|
1392
|
+
|
|
1393
|
+
if (!this.spec.Paths || this.spec.Paths.length === 0) {
|
|
1394
|
+
console.log('🔶 saveStepPaths: No paths to save, returning early');
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
console.log(`🔶 saveStepPaths: Processing ${this.spec.Paths.length} path(s)...`);
|
|
1399
|
+
const md = new Metadata();
|
|
1400
|
+
|
|
1401
|
+
// Create a map of step names to IDs for easy lookup
|
|
1402
|
+
const stepNameToId = new Map<string, string>();
|
|
1403
|
+
if (this.spec.Steps) {
|
|
1404
|
+
for (const step of this.spec.Steps) {
|
|
1405
|
+
if (step.ID) {
|
|
1406
|
+
stepNameToId.set(step.Name, step.ID);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
for (const pathSpec of this.spec.Paths) {
|
|
1412
|
+
const pathEntity = await md.GetEntityObject<AIAgentStepPathEntity>(
|
|
1413
|
+
'MJ: AI Agent Step Paths',
|
|
1414
|
+
this._contextUser
|
|
1415
|
+
);
|
|
1416
|
+
|
|
1417
|
+
// Load existing if ID provided
|
|
1418
|
+
const isUpdate = !!(pathSpec.ID && pathSpec.ID !== '');
|
|
1419
|
+
if (isUpdate) {
|
|
1420
|
+
await pathEntity.Load(pathSpec.ID);
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
// Resolve step IDs from step names
|
|
1424
|
+
// OriginStepID and DestinationStepID can be either step IDs or step names
|
|
1425
|
+
// If they're names, look them up in our map
|
|
1426
|
+
let originStepId = pathSpec.OriginStepID;
|
|
1427
|
+
let destinationStepId = pathSpec.DestinationStepID;
|
|
1428
|
+
|
|
1429
|
+
// Try to resolve as step names first
|
|
1430
|
+
if (stepNameToId.has(pathSpec.OriginStepID)) {
|
|
1431
|
+
originStepId = stepNameToId.get(pathSpec.OriginStepID)!;
|
|
1432
|
+
}
|
|
1433
|
+
if (stepNameToId.has(pathSpec.DestinationStepID)) {
|
|
1434
|
+
destinationStepId = stepNameToId.get(pathSpec.DestinationStepID)!;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// Map fields
|
|
1438
|
+
pathEntity.OriginStepID = originStepId;
|
|
1439
|
+
pathEntity.DestinationStepID = destinationStepId;
|
|
1440
|
+
pathEntity.Condition = pathSpec.Condition || null;
|
|
1441
|
+
pathEntity.Description = pathSpec.Description || null;
|
|
1442
|
+
pathEntity.Priority = pathSpec.Priority;
|
|
1443
|
+
|
|
1444
|
+
const saved = await pathEntity.Save();
|
|
1445
|
+
if (!saved) {
|
|
1446
|
+
throw new Error(`Failed to save path from "${pathSpec.OriginStepID}" to "${pathSpec.DestinationStepID}"`);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// Track mutation
|
|
1450
|
+
this.trackMutation(
|
|
1451
|
+
'AI Agent Step Paths',
|
|
1452
|
+
isUpdate ? 'Update' : 'Create',
|
|
1453
|
+
pathEntity.ID,
|
|
1454
|
+
`${isUpdate ? 'Updated' : 'Created'} path from "${pathSpec.OriginStepID}" to "${pathSpec.DestinationStepID}"`
|
|
1455
|
+
);
|
|
1456
|
+
|
|
1457
|
+
// Update the spec with the saved ID
|
|
1458
|
+
pathSpec.ID = pathEntity.ID;
|
|
1459
|
+
|
|
1460
|
+
console.log(`✅ saveStepPaths: Created AIAgentStepPath with ID: ${pathEntity.ID}`);
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
console.log(`✅ saveStepPaths: Successfully saved all ${this.spec.Paths.length} path(s)`);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
750
1466
|
// ===== UTILITY METHODS =====
|
|
751
1467
|
|
|
752
1468
|
/**
|
|
@@ -784,6 +1500,11 @@ export class AgentSpecSync {
|
|
|
784
1500
|
ID: partial.ID || '',
|
|
785
1501
|
Name: partial.Name || '',
|
|
786
1502
|
Description: partial.Description,
|
|
1503
|
+
|
|
1504
|
+
// TypeID and Status are extended fields (not in base AgentSpec interface)
|
|
1505
|
+
TypeID: (partial as any).TypeID,
|
|
1506
|
+
Status: (partial as any).Status || 'Active',
|
|
1507
|
+
|
|
787
1508
|
IconClass: partial.IconClass,
|
|
788
1509
|
LogoURL: partial.LogoURL,
|
|
789
1510
|
ParentID: partial.ParentID,
|
|
@@ -804,14 +1525,21 @@ export class AgentSpecSync {
|
|
|
804
1525
|
MinExecutionsPerRun: partial.MinExecutionsPerRun,
|
|
805
1526
|
MaxExecutionsPerRun: partial.MaxExecutionsPerRun,
|
|
806
1527
|
StartingPayloadValidation: partial.StartingPayloadValidation,
|
|
807
|
-
StartingPayloadValidationMode: partial.StartingPayloadValidationMode ||
|
|
1528
|
+
StartingPayloadValidationMode: partial.StartingPayloadValidationMode || 'Fail',
|
|
808
1529
|
DefaultPromptEffortLevel: partial.DefaultPromptEffortLevel,
|
|
809
1530
|
ChatHandlingOption: partial.ChatHandlingOption,
|
|
810
1531
|
DefaultArtifactTypeID: partial.DefaultArtifactTypeID,
|
|
811
1532
|
OwnerUserID: partial.OwnerUserID,
|
|
812
1533
|
InvocationMode: partial.InvocationMode || 'Any',
|
|
1534
|
+
FunctionalRequirements: partial.FunctionalRequirements,
|
|
1535
|
+
TechnicalDesign: partial.TechnicalDesign,
|
|
813
1536
|
Actions: partial.Actions || [],
|
|
814
|
-
SubAgents: partial.SubAgents || []
|
|
1537
|
+
SubAgents: partial.SubAgents || [],
|
|
1538
|
+
Prompts: partial.Prompts || [],
|
|
1539
|
+
|
|
1540
|
+
// Flow agent fields - critical for Flow agent support
|
|
1541
|
+
Steps: partial.Steps || [],
|
|
1542
|
+
Paths: partial.Paths || []
|
|
815
1543
|
};
|
|
816
1544
|
}
|
|
817
1545
|
|
|
@@ -871,4 +1599,4 @@ export class AgentSpecSync {
|
|
|
871
1599
|
public markDirty(): void {
|
|
872
1600
|
this._isDirty = true;
|
|
873
1601
|
}
|
|
874
|
-
}
|
|
1602
|
+
}
|