@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.
@@ -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: () => 'Fail',
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
- const [actionsResult, childAgentsResult, relatedAgentsResult] = await rv.RunViews([
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
- // Note: We don't recursively populate the full spec here since SubAgentSpec
302
- // only contains the ID and relationship metadata. To get full details,
303
- // users can call LoadFromDatabase on the SubAgentID separately if needed.
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
- SubAgentID: childAgent.ID
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
- SubAgentID: relationship.SubAgentID,
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 the saved agent ID
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 agentId = await spec.SaveToDatabase();
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<string> {
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 this.spec.ID;
735
+ return {
736
+ agentId: this.spec.ID,
737
+ success: true,
738
+ mutations: []
739
+ };
468
740
  }
469
741
 
470
- // Step 1: Save main agent entity
471
- const agentId = await this.saveAgentEntity(validate);
742
+ try {
743
+ // Step 1: Save main agent entity
744
+ const agentId = await this.saveAgentEntity(validate);
472
745
 
473
- // Step 2: Save actions
474
- await this.saveActions(agentId);
746
+ // Step 2: Save actions
747
+ await this.saveActions(agentId);
475
748
 
476
- // Step 3: Save sub-agents (both child and related)
477
- await this.saveSubAgents(agentId);
749
+ // Step 3: Save sub-agents (both child and related)
750
+ await this.saveSubAgents(agentId);
478
751
 
479
- this._isDirty = false;
480
- this._isLoaded = true;
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
- return agentId;
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?.() || 'Fail';
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 SubAgentID is empty, this indicates a new
659
- * child agent that needs to be created.
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 doesn't exist or save fails
1000
+ * @throws {Error} If child agent save fails
665
1001
  */
666
1002
  private async saveChildSubAgent(parentId: string, SubAgentSpec: SubAgentSpec): Promise<void> {
667
- if (!SubAgentSpec.SubAgentID) {
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
- // For child agents, we just need to ensure the ParentID is set correctly
672
- // The sub-agent itself should already exist or be created separately
673
- const md = new Metadata();
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
- const loaded = await childEntity.Load(SubAgentSpec.SubAgentID);
680
- if (!loaded) {
681
- throw new Error(`Child agent ${SubAgentSpec.SubAgentID} not found`);
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
- // Update parent ID if needed
685
- if (childEntity.ParentID !== parentId) {
686
- childEntity.ParentID = parentId;
687
- const saved = await childEntity.Save();
688
- if (!saved) {
689
- throw new Error(`Failed to update ParentID for child agent ${SubAgentSpec.SubAgentID}`);
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
- if (SubAgentSpec.AgentRelationshipID) {
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.SubAgentID;
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.SubAgentID}`);
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 || (() => 'Fail'),
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
+ }