@memberjunction/ai-agent-manager 6.2.0-edge.0 → 6.2.0-edge.2
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 +125 -0
- package/dist/agent-spec-sync.d.ts +17 -1
- package/dist/agent-spec-sync.d.ts.map +1 -1
- package/dist/agent-spec-sync.js +128 -93
- package/dist/agent-spec-sync.js.map +1 -1
- package/dist/agents/architect-agent.d.ts +28 -0
- package/dist/agents/architect-agent.d.ts.map +1 -1
- package/dist/agents/architect-agent.js +123 -76
- package/dist/agents/architect-agent.js.map +1 -1
- package/dist/flow-step-validation.d.ts +56 -3
- package/dist/flow-step-validation.d.ts.map +1 -1
- package/dist/flow-step-validation.js +214 -0
- package/dist/flow-step-validation.js.map +1 -1
- package/dist/workflow-agent-writer.d.ts +9 -2
- package/dist/workflow-agent-writer.d.ts.map +1 -1
- package/dist/workflow-agent-writer.js +30 -6
- package/dist/workflow-agent-writer.js.map +1 -1
- package/package.json +9 -9
- package/src/__tests__/agent-spec-sync-decision-roundtrip.test.ts +218 -0
- package/src/__tests__/architect-agent-example.test.ts +218 -0
- package/src/__tests__/flow-step-validation.test.ts +328 -8
- package/src/__tests__/workflow-agent-writer.test.ts +62 -0
- package/src/agent-spec-sync.ts +136 -94
- package/src/agents/architect-agent.ts +131 -82
- package/src/flow-step-validation.ts +254 -3
- package/src/workflow-agent-writer.ts +33 -8
package/dist/agent-spec-sync.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Metadata, RunView, LogError } from '@memberjunction/core';
|
|
2
2
|
import { UUIDsEqual } from '@memberjunction/global';
|
|
3
|
+
import { StepConfigurationText } from './flow-step-validation.js';
|
|
3
4
|
/**
|
|
4
5
|
* AgentSpecSync provides a high-level interface for working with AI Agent metadata in MemberJunction.
|
|
5
6
|
*
|
|
@@ -51,6 +52,14 @@ import { UUIDsEqual } from '@memberjunction/global';
|
|
|
51
52
|
* @module @memberjunction/ai-agent-manager
|
|
52
53
|
*/
|
|
53
54
|
export class AgentSpecSync {
|
|
55
|
+
/** @deprecated Use {@link Spec}. */
|
|
56
|
+
get spec() {
|
|
57
|
+
return this.Spec;
|
|
58
|
+
}
|
|
59
|
+
/** @deprecated Use {@link Spec}. */
|
|
60
|
+
set spec(value) {
|
|
61
|
+
this.Spec = value;
|
|
62
|
+
}
|
|
54
63
|
/**
|
|
55
64
|
* Returns the metadata provider to use for entity operations. Prefers a
|
|
56
65
|
* caller-supplied provider for server isolation; otherwise falls back to
|
|
@@ -87,12 +96,12 @@ export class AgentSpecSync {
|
|
|
87
96
|
*/
|
|
88
97
|
this._mutations = [];
|
|
89
98
|
if (spec) {
|
|
90
|
-
this.
|
|
99
|
+
this.Spec = this.initializeSpec(spec);
|
|
91
100
|
this._isDirty = true;
|
|
92
101
|
}
|
|
93
102
|
else {
|
|
94
103
|
// Create minimal empty spec
|
|
95
|
-
this.
|
|
104
|
+
this.Spec = {
|
|
96
105
|
ID: '', // Will be set on save if empty
|
|
97
106
|
Name: '',
|
|
98
107
|
StartingPayloadValidationMode: 'Fail',
|
|
@@ -120,15 +129,23 @@ export class AgentSpecSync {
|
|
|
120
129
|
* Get all mutations tracked during the last save operation
|
|
121
130
|
* @returns Array of mutations
|
|
122
131
|
*/
|
|
123
|
-
|
|
132
|
+
GetMutations() {
|
|
124
133
|
return [...this._mutations];
|
|
125
134
|
}
|
|
135
|
+
/** @deprecated Use {@link GetMutations}. */
|
|
136
|
+
getMutations() {
|
|
137
|
+
return this.GetMutations();
|
|
138
|
+
}
|
|
126
139
|
/**
|
|
127
140
|
* Clear all tracked mutations
|
|
128
141
|
*/
|
|
129
|
-
|
|
142
|
+
ClearMutations() {
|
|
130
143
|
this._mutations = [];
|
|
131
144
|
}
|
|
145
|
+
/** @deprecated Use {@link ClearMutations}. */
|
|
146
|
+
clearMutations() {
|
|
147
|
+
return this.ClearMutations();
|
|
148
|
+
}
|
|
132
149
|
// ===== STATIC FACTORY METHODS =====
|
|
133
150
|
/**
|
|
134
151
|
* Load an agent and its complete hierarchy from the database by ID.
|
|
@@ -342,11 +359,11 @@ export class AgentSpecSync {
|
|
|
342
359
|
};
|
|
343
360
|
}
|
|
344
361
|
// Step 3: Map entities to raw spec format
|
|
345
|
-
this.
|
|
362
|
+
this.Spec = this.mapEntitiesToRawSpec(agentEntity, actionsResult.Results || [], childAgentsResult.Results || [], relatedAgentsResult.Results || [], promptsResult.Results || [], fullPromptsResult.Results || [], stepsResult.Results || [], pathsResult.Results || []);
|
|
346
363
|
// Step 4: Recursively load sub-agents if requested
|
|
347
|
-
if (includeSubAgents && this.
|
|
364
|
+
if (includeSubAgents && this.Spec.SubAgents && this.Spec.SubAgents.length > 0) {
|
|
348
365
|
// Recursively load complete specs for all sub-agents
|
|
349
|
-
for (const subAgentSpec of this.
|
|
366
|
+
for (const subAgentSpec of this.Spec.SubAgents) {
|
|
350
367
|
if (subAgentSpec.SubAgent && subAgentSpec.SubAgent.ID) {
|
|
351
368
|
try {
|
|
352
369
|
// Load the complete sub-agent spec recursively
|
|
@@ -550,9 +567,9 @@ export class AgentSpecSync {
|
|
|
550
567
|
ActionID: step.ActionID || undefined,
|
|
551
568
|
SubAgentID: step.SubAgentID || undefined,
|
|
552
569
|
PromptID: step.PromptID || undefined,
|
|
553
|
-
// Loop steps round-trip their
|
|
554
|
-
// that contains a loop and writing it back would silently
|
|
555
|
-
//
|
|
570
|
+
// Loop & Decision steps round-trip their configuration and bounds; without these, reading
|
|
571
|
+
// a flow agent that contains a decision or loop and writing it back would silently strip
|
|
572
|
+
// the step's logic or bounds.
|
|
556
573
|
LoopBodyType: step.LoopBodyType || undefined,
|
|
557
574
|
Configuration: step.Configuration || undefined,
|
|
558
575
|
ActionInputMapping: this.parseJsonField(step.ActionInputMapping),
|
|
@@ -604,22 +621,22 @@ export class AgentSpecSync {
|
|
|
604
621
|
*/
|
|
605
622
|
async SaveToDatabase(validate = true) {
|
|
606
623
|
// Clear previous mutations
|
|
607
|
-
this.
|
|
624
|
+
this.ClearMutations();
|
|
608
625
|
if (!this._isDirty && this._isLoaded) {
|
|
609
626
|
// No changes to save
|
|
610
627
|
return {
|
|
611
|
-
agentId: this.
|
|
628
|
+
agentId: this.Spec.ID,
|
|
612
629
|
success: true,
|
|
613
630
|
mutations: []
|
|
614
631
|
};
|
|
615
632
|
}
|
|
616
633
|
try {
|
|
617
634
|
// Step 0: Delete orphaned records (if this is an update)
|
|
618
|
-
if (this._isLoaded && this.
|
|
619
|
-
console.log(`🗑️ SaveToDatabase: Loading current database state for agent ${this.
|
|
620
|
-
const dbState = await this.loadCurrentDatabaseState(this.
|
|
635
|
+
if (this._isLoaded && this.Spec.ID) {
|
|
636
|
+
console.log(`🗑️ SaveToDatabase: Loading current database state for agent ${this.Spec.ID}...`);
|
|
637
|
+
const dbState = await this.loadCurrentDatabaseState(this.Spec.ID);
|
|
621
638
|
console.log(`🔍 SaveToDatabase: Identifying orphaned records...`);
|
|
622
|
-
const orphans = this.identifyOrphans(dbState, this.
|
|
639
|
+
const orphans = this.identifyOrphans(dbState, this.Spec);
|
|
623
640
|
console.log(`🗑️ SaveToDatabase: Deleting ${orphans.paths.length} paths, ${orphans.actions.length} actions, ${orphans.steps.length} steps, ${orphans.prompts.length} prompts, ${orphans.relationships.length} relationships, orphaning ${orphans.childAgents.length} child agents...`);
|
|
624
641
|
await this.deleteOrphans(orphans);
|
|
625
642
|
console.log(`✅ SaveToDatabase: Orphan cleanup complete`);
|
|
@@ -641,15 +658,15 @@ export class AgentSpecSync {
|
|
|
641
658
|
return {
|
|
642
659
|
agentId,
|
|
643
660
|
success: true,
|
|
644
|
-
mutations: this.
|
|
661
|
+
mutations: this.GetMutations()
|
|
645
662
|
};
|
|
646
663
|
}
|
|
647
664
|
catch (error) {
|
|
648
665
|
// Return failure with whatever mutations were completed before error
|
|
649
666
|
return {
|
|
650
|
-
agentId: this.
|
|
667
|
+
agentId: this.Spec.ID || '',
|
|
651
668
|
success: false,
|
|
652
|
-
mutations: this.
|
|
669
|
+
mutations: this.GetMutations()
|
|
653
670
|
};
|
|
654
671
|
}
|
|
655
672
|
}
|
|
@@ -665,69 +682,69 @@ export class AgentSpecSync {
|
|
|
665
682
|
const md = this.providerToUse;
|
|
666
683
|
const agentEntity = await md.GetEntityObject('MJ: AI Agents', this._contextUser);
|
|
667
684
|
// Track if this is an update or create
|
|
668
|
-
const isUpdate = !!this.
|
|
685
|
+
const isUpdate = !!this.Spec.ID;
|
|
669
686
|
// If ID exists, load existing record
|
|
670
|
-
if (this.
|
|
671
|
-
const loaded = await agentEntity.Load(this.
|
|
687
|
+
if (this.Spec.ID) {
|
|
688
|
+
const loaded = await agentEntity.Load(this.Spec.ID);
|
|
672
689
|
if (!loaded) {
|
|
673
|
-
throw new Error(`Cannot update non-existent agent with ID ${this.
|
|
690
|
+
throw new Error(`Cannot update non-existent agent with ID ${this.Spec.ID}`);
|
|
674
691
|
}
|
|
675
692
|
}
|
|
676
693
|
// Map spec to entity fields
|
|
677
|
-
agentEntity.Name = this.
|
|
678
|
-
agentEntity.Description = this.
|
|
679
|
-
agentEntity.IconClass = this.
|
|
680
|
-
agentEntity.LogoURL = this.
|
|
681
|
-
agentEntity.ParentID = this.
|
|
682
|
-
agentEntity.DriverClass = this.
|
|
683
|
-
agentEntity.ModelSelectionMode = this.
|
|
694
|
+
agentEntity.Name = this.Spec.Name;
|
|
695
|
+
agentEntity.Description = this.Spec.Description || null;
|
|
696
|
+
agentEntity.IconClass = this.Spec.IconClass || null;
|
|
697
|
+
agentEntity.LogoURL = this.Spec.LogoURL || null;
|
|
698
|
+
agentEntity.ParentID = this.Spec.ParentID || null;
|
|
699
|
+
agentEntity.DriverClass = this.Spec.DriverClass || null;
|
|
700
|
+
agentEntity.ModelSelectionMode = this.Spec.ModelSelectionMode || 'Agent Type';
|
|
684
701
|
// Handle TypeID - supports lookup references or direct GUID
|
|
685
|
-
if (this.
|
|
686
|
-
agentEntity.TypeID = this.
|
|
702
|
+
if (this.Spec.TypeID) {
|
|
703
|
+
agentEntity.TypeID = this.Spec.TypeID;
|
|
687
704
|
}
|
|
688
705
|
// Handle Status - defaults to Active if not specified. Typed rather than cast: the cast is
|
|
689
706
|
// what let `'Inactive'` — a value the CHECK constraint rejects — reach the entity unchallenged.
|
|
690
|
-
agentEntity.Status = this.
|
|
707
|
+
agentEntity.Status = this.Spec.Status ?? 'Active';
|
|
691
708
|
// Serialize JSON fields
|
|
692
|
-
agentEntity.PayloadDownstreamPaths = JSON.stringify(this.
|
|
693
|
-
agentEntity.PayloadUpstreamPaths = JSON.stringify(this.
|
|
694
|
-
agentEntity.PayloadSelfReadPaths = this.
|
|
695
|
-
? JSON.stringify(this.
|
|
709
|
+
agentEntity.PayloadDownstreamPaths = JSON.stringify(this.Spec.PayloadDownstreamPaths || ['*']);
|
|
710
|
+
agentEntity.PayloadUpstreamPaths = JSON.stringify(this.Spec.PayloadUpstreamPaths || ['*']);
|
|
711
|
+
agentEntity.PayloadSelfReadPaths = this.Spec.PayloadSelfReadPaths
|
|
712
|
+
? JSON.stringify(this.Spec.PayloadSelfReadPaths)
|
|
696
713
|
: null;
|
|
697
|
-
agentEntity.PayloadSelfWritePaths = this.
|
|
698
|
-
? JSON.stringify(this.
|
|
714
|
+
agentEntity.PayloadSelfWritePaths = this.Spec.PayloadSelfWritePaths
|
|
715
|
+
? JSON.stringify(this.Spec.PayloadSelfWritePaths)
|
|
699
716
|
: null;
|
|
700
|
-
agentEntity.PayloadScope = this.
|
|
717
|
+
agentEntity.PayloadScope = this.Spec.PayloadScope || null;
|
|
701
718
|
// Validation fields
|
|
702
|
-
agentEntity.FinalPayloadValidation = this.
|
|
703
|
-
agentEntity.FinalPayloadValidationMode = this.
|
|
704
|
-
agentEntity.FinalPayloadValidationMaxRetries = this.
|
|
705
|
-
agentEntity.StartingPayloadValidation = this.
|
|
706
|
-
agentEntity.StartingPayloadValidationMode = this.
|
|
719
|
+
agentEntity.FinalPayloadValidation = this.Spec.FinalPayloadValidation || null;
|
|
720
|
+
agentEntity.FinalPayloadValidationMode = this.Spec.FinalPayloadValidationMode || 'Retry';
|
|
721
|
+
agentEntity.FinalPayloadValidationMaxRetries = this.Spec.FinalPayloadValidationMaxRetries || 3;
|
|
722
|
+
agentEntity.StartingPayloadValidation = this.Spec.StartingPayloadValidation || null;
|
|
723
|
+
agentEntity.StartingPayloadValidationMode = this.Spec.StartingPayloadValidationMode || 'Fail';
|
|
707
724
|
// Resource limits
|
|
708
|
-
agentEntity.MaxCostPerRun = this.
|
|
709
|
-
agentEntity.MaxTokensPerRun = this.
|
|
710
|
-
agentEntity.MaxIterationsPerRun = this.
|
|
711
|
-
agentEntity.MaxTimePerRun = this.
|
|
725
|
+
agentEntity.MaxCostPerRun = this.Spec.MaxCostPerRun || null;
|
|
726
|
+
agentEntity.MaxTokensPerRun = this.Spec.MaxTokensPerRun || null;
|
|
727
|
+
agentEntity.MaxIterationsPerRun = this.Spec.MaxIterationsPerRun || null;
|
|
728
|
+
agentEntity.MaxTimePerRun = this.Spec.MaxTimePerRun || null;
|
|
712
729
|
// Execution frequency
|
|
713
|
-
agentEntity.MinExecutionsPerRun = this.
|
|
714
|
-
agentEntity.MaxExecutionsPerRun = this.
|
|
730
|
+
agentEntity.MinExecutionsPerRun = this.Spec.MinExecutionsPerRun || null;
|
|
731
|
+
agentEntity.MaxExecutionsPerRun = this.Spec.MaxExecutionsPerRun || null;
|
|
715
732
|
// Other config
|
|
716
|
-
agentEntity.DefaultPromptEffortLevel = this.
|
|
717
|
-
agentEntity.ChatHandlingOption = this.
|
|
718
|
-
agentEntity.DefaultArtifactTypeID = this.
|
|
733
|
+
agentEntity.DefaultPromptEffortLevel = this.Spec.DefaultPromptEffortLevel || null;
|
|
734
|
+
agentEntity.ChatHandlingOption = this.Spec.ChatHandlingOption || null;
|
|
735
|
+
agentEntity.DefaultArtifactTypeID = this.Spec.DefaultArtifactTypeID || null;
|
|
719
736
|
// Set OwnerUserID - always use contextUser if available (user creating/modifying the agent)
|
|
720
737
|
if (this._contextUser) {
|
|
721
738
|
agentEntity.OwnerUserID = this._contextUser.ID;
|
|
722
739
|
}
|
|
723
|
-
else if (this.
|
|
740
|
+
else if (this.Spec.OwnerUserID) {
|
|
724
741
|
// Fallback to spec value if no contextUser provided
|
|
725
|
-
agentEntity.OwnerUserID = this.
|
|
742
|
+
agentEntity.OwnerUserID = this.Spec.OwnerUserID;
|
|
726
743
|
}
|
|
727
|
-
agentEntity.InvocationMode = this.
|
|
744
|
+
agentEntity.InvocationMode = this.Spec.InvocationMode || 'Any';
|
|
728
745
|
// Requirements and design documentation
|
|
729
|
-
agentEntity.FunctionalRequirements = this.
|
|
730
|
-
agentEntity.TechnicalDesign = this.
|
|
746
|
+
agentEntity.FunctionalRequirements = this.Spec.FunctionalRequirements || null;
|
|
747
|
+
agentEntity.TechnicalDesign = this.Spec.TechnicalDesign || null;
|
|
731
748
|
// Validate if requested
|
|
732
749
|
if (validate) {
|
|
733
750
|
const validation = agentEntity.Validate();
|
|
@@ -742,9 +759,9 @@ export class AgentSpecSync {
|
|
|
742
759
|
throw new Error('Failed to save agent entity');
|
|
743
760
|
}
|
|
744
761
|
// Update spec with saved ID
|
|
745
|
-
this.
|
|
762
|
+
this.Spec.ID = agentEntity.ID;
|
|
746
763
|
// Track the mutation
|
|
747
|
-
this.trackMutation('MJ: AI Agents', isUpdate ? 'Update' : 'Create', agentEntity.ID, `${isUpdate ? 'Updated' : 'Created'} agent: ${this.
|
|
764
|
+
this.trackMutation('MJ: AI Agents', isUpdate ? 'Update' : 'Create', agentEntity.ID, `${isUpdate ? 'Updated' : 'Created'} agent: ${this.Spec.Name}`);
|
|
748
765
|
return agentEntity.ID;
|
|
749
766
|
}
|
|
750
767
|
/**
|
|
@@ -759,11 +776,11 @@ export class AgentSpecSync {
|
|
|
759
776
|
* @throws {Error} If any action save fails
|
|
760
777
|
*/
|
|
761
778
|
async saveActions(agentId) {
|
|
762
|
-
if (!this.
|
|
779
|
+
if (!this.Spec.Actions || this.Spec.Actions.length === 0) {
|
|
763
780
|
return;
|
|
764
781
|
}
|
|
765
782
|
const md = this.providerToUse;
|
|
766
|
-
for (const actionSpec of this.
|
|
783
|
+
for (const actionSpec of this.Spec.Actions) {
|
|
767
784
|
const actionEntity = await md.GetEntityObject('MJ: AI Agent Actions', this._contextUser);
|
|
768
785
|
// Track if this is an update or create
|
|
769
786
|
const isUpdate = !!actionSpec.AgentActionID;
|
|
@@ -803,10 +820,10 @@ export class AgentSpecSync {
|
|
|
803
820
|
* @throws {Error} If any sub-agent save fails
|
|
804
821
|
*/
|
|
805
822
|
async saveSubAgents(agentId) {
|
|
806
|
-
if (!this.
|
|
823
|
+
if (!this.Spec.SubAgents || this.Spec.SubAgents.length === 0) {
|
|
807
824
|
return;
|
|
808
825
|
}
|
|
809
|
-
for (const SubAgentSpec of this.
|
|
826
|
+
for (const SubAgentSpec of this.Spec.SubAgents) {
|
|
810
827
|
if (SubAgentSpec.Type === 'child') {
|
|
811
828
|
await this.saveChildSubAgent(agentId, SubAgentSpec);
|
|
812
829
|
}
|
|
@@ -928,17 +945,17 @@ export class AgentSpecSync {
|
|
|
928
945
|
* @throws {Error} If any prompt save fails
|
|
929
946
|
*/
|
|
930
947
|
async savePrompts(agentId) {
|
|
931
|
-
console.log(`💬 savePrompts: Called with agentId=${agentId}, Prompts=${this.
|
|
932
|
-
if (!this.
|
|
948
|
+
console.log(`💬 savePrompts: Called with agentId=${agentId}, Prompts=${this.Spec.Prompts ? this.Spec.Prompts.length : 'undefined'}`);
|
|
949
|
+
if (!this.Spec.Prompts || this.Spec.Prompts.length === 0) {
|
|
933
950
|
console.log('💬 savePrompts: No prompts to save, returning early');
|
|
934
951
|
return;
|
|
935
952
|
}
|
|
936
|
-
console.log(`💬 savePrompts: Processing ${this.
|
|
953
|
+
console.log(`💬 savePrompts: Processing ${this.Spec.Prompts.length} prompt(s)...`);
|
|
937
954
|
const md = this.providerToUse;
|
|
938
955
|
const rv = RunView.FromMetadataProvider(this._provider);
|
|
939
956
|
// Step 1: Create or update AIPrompt records
|
|
940
|
-
for (let i = 0; i < this.
|
|
941
|
-
const promptSpec = this.
|
|
957
|
+
for (let i = 0; i < this.Spec.Prompts.length; i++) {
|
|
958
|
+
const promptSpec = this.Spec.Prompts[i];
|
|
942
959
|
// Check if this is an update (has PromptID) or create (no PromptID)
|
|
943
960
|
let promptEntity = await md.GetEntityObject('MJ: AI Prompts', this._contextUser);
|
|
944
961
|
let isUpdate = false;
|
|
@@ -954,8 +971,8 @@ export class AgentSpecSync {
|
|
|
954
971
|
}
|
|
955
972
|
}
|
|
956
973
|
// Set all required fields (both create and update)
|
|
957
|
-
promptEntity.Name = `${this.
|
|
958
|
-
promptEntity.Description = `Agent prompt ${i + 1} for ${this.
|
|
974
|
+
promptEntity.Name = `${this.Spec.Name} - Prompt ${i + 1}`;
|
|
975
|
+
promptEntity.Description = `Agent prompt ${i + 1} for ${this.Spec.Name}`;
|
|
959
976
|
promptEntity.TypeID = 'a6da423e-f36b-1410-8dac-00021f8b792e'; // Chat type
|
|
960
977
|
promptEntity.Status = 'Active';
|
|
961
978
|
promptEntity.ResponseFormat = 'JSON';
|
|
@@ -981,7 +998,7 @@ export class AgentSpecSync {
|
|
|
981
998
|
}
|
|
982
999
|
const saved = await promptEntity.Save();
|
|
983
1000
|
if (!saved) {
|
|
984
|
-
throw new Error(`Failed to save prompt ${i + 1} for agent ${this.
|
|
1001
|
+
throw new Error(`Failed to save prompt ${i + 1} for agent ${this.Spec.Name}`);
|
|
985
1002
|
}
|
|
986
1003
|
// Track mutation
|
|
987
1004
|
this.trackMutation('MJ: AI Prompts', isUpdate ? 'Update' : 'Create', promptEntity.ID, `${isUpdate ? 'Updated' : 'Created'} prompt: ${promptEntity.Name}`);
|
|
@@ -1021,7 +1038,7 @@ export class AgentSpecSync {
|
|
|
1021
1038
|
this.trackMutation('MJ: AI Agent Prompts', isJunctionUpdate ? 'Update' : 'Create', agentPromptEntity.ID, `${isJunctionUpdate ? 'Updated' : 'Created'} agent-prompt junction for prompt ${promptEntity.ID}`);
|
|
1022
1039
|
console.log(`✅ savePrompts: ${isJunctionUpdate ? 'Updated' : 'Created'} AIAgentPrompt junction with ID: ${agentPromptEntity.ID}`);
|
|
1023
1040
|
}
|
|
1024
|
-
console.log(`✅ savePrompts: Successfully saved all ${this.
|
|
1041
|
+
console.log(`✅ savePrompts: Successfully saved all ${this.Spec.Prompts.length} prompt(s)`);
|
|
1025
1042
|
}
|
|
1026
1043
|
/**
|
|
1027
1044
|
* Save all steps for this agent (Flow agents only).
|
|
@@ -1034,14 +1051,14 @@ export class AgentSpecSync {
|
|
|
1034
1051
|
* @throws {Error} If any step save fails
|
|
1035
1052
|
*/
|
|
1036
1053
|
async saveSteps(agentId) {
|
|
1037
|
-
console.log(`🔷 saveSteps: Called with agentId=${agentId}, Steps=${this.
|
|
1038
|
-
if (!this.
|
|
1054
|
+
console.log(`🔷 saveSteps: Called with agentId=${agentId}, Steps=${this.Spec.Steps ? this.Spec.Steps.length : 'undefined'}`);
|
|
1055
|
+
if (!this.Spec.Steps || this.Spec.Steps.length === 0) {
|
|
1039
1056
|
console.log('🔷 saveSteps: No steps to save, returning early');
|
|
1040
1057
|
return;
|
|
1041
1058
|
}
|
|
1042
|
-
console.log(`🔷 saveSteps: Processing ${this.
|
|
1059
|
+
console.log(`🔷 saveSteps: Processing ${this.Spec.Steps.length} step(s)...`);
|
|
1043
1060
|
const md = this.providerToUse;
|
|
1044
|
-
for (const stepSpec of this.
|
|
1061
|
+
for (const stepSpec of this.Spec.Steps) {
|
|
1045
1062
|
const stepEntity = await md.GetEntityObject('MJ: AI Agent Steps', this._contextUser);
|
|
1046
1063
|
// Load existing if ID provided
|
|
1047
1064
|
const isUpdate = !!(stepSpec.ID && stepSpec.ID !== '');
|
|
@@ -1061,7 +1078,9 @@ export class AgentSpecSync {
|
|
|
1061
1078
|
// Configuration carries the bounds. Written for every step because a step that STOPS
|
|
1062
1079
|
// being a loop must have these cleared, not left behind from its previous shape.
|
|
1063
1080
|
stepEntity.LoopBodyType = stepSpec.LoopBodyType || null;
|
|
1064
|
-
|
|
1081
|
+
// Stored as JSON text even when the model wrote an object, with a Decision step's aliases
|
|
1082
|
+
// normalized: the text the Architect's validator compiled.
|
|
1083
|
+
stepEntity.Configuration = StepConfigurationText(stepSpec);
|
|
1065
1084
|
// Handle inline prompt creation for Prompt-type steps
|
|
1066
1085
|
// If StepType is Prompt and PromptID is empty, create a new AIPrompt record
|
|
1067
1086
|
if (stepSpec.StepType === 'Prompt' && (!stepSpec.PromptID || stepSpec.PromptID === '')) {
|
|
@@ -1116,7 +1135,7 @@ export class AgentSpecSync {
|
|
|
1116
1135
|
}
|
|
1117
1136
|
const saved = await stepEntity.Save();
|
|
1118
1137
|
if (!saved) {
|
|
1119
|
-
throw new Error(`Failed to save step "${stepSpec.Name}" for agent ${this.
|
|
1138
|
+
throw new Error(`Failed to save step "${stepSpec.Name}" for agent ${this.Spec.Name}`);
|
|
1120
1139
|
}
|
|
1121
1140
|
// Track mutation
|
|
1122
1141
|
this.trackMutation('AI Agent Steps', isUpdate ? 'Update' : 'Create', stepEntity.ID, `${isUpdate ? 'Updated' : 'Created'} step: ${stepSpec.Name}`);
|
|
@@ -1124,7 +1143,7 @@ export class AgentSpecSync {
|
|
|
1124
1143
|
stepSpec.ID = stepEntity.ID;
|
|
1125
1144
|
console.log(`✅ saveSteps: Created AIAgentStep with ID: ${stepEntity.ID}`);
|
|
1126
1145
|
}
|
|
1127
|
-
console.log(`✅ saveSteps: Successfully saved all ${this.
|
|
1146
|
+
console.log(`✅ saveSteps: Successfully saved all ${this.Spec.Steps.length} step(s)`);
|
|
1128
1147
|
}
|
|
1129
1148
|
/**
|
|
1130
1149
|
* Save all step paths for this agent (Flow agents only).
|
|
@@ -1137,23 +1156,23 @@ export class AgentSpecSync {
|
|
|
1137
1156
|
* @throws {Error} If any path save fails
|
|
1138
1157
|
*/
|
|
1139
1158
|
async saveStepPaths(agentId) {
|
|
1140
|
-
console.log(`🔶 saveStepPaths: Called with agentId=${agentId}, Paths=${this.
|
|
1141
|
-
if (!this.
|
|
1159
|
+
console.log(`🔶 saveStepPaths: Called with agentId=${agentId}, Paths=${this.Spec.Paths ? this.Spec.Paths.length : 'undefined'}`);
|
|
1160
|
+
if (!this.Spec.Paths || this.Spec.Paths.length === 0) {
|
|
1142
1161
|
console.log('🔶 saveStepPaths: No paths to save, returning early');
|
|
1143
1162
|
return;
|
|
1144
1163
|
}
|
|
1145
|
-
console.log(`🔶 saveStepPaths: Processing ${this.
|
|
1164
|
+
console.log(`🔶 saveStepPaths: Processing ${this.Spec.Paths.length} path(s)...`);
|
|
1146
1165
|
const md = this.providerToUse;
|
|
1147
1166
|
// Create a map of step names to IDs for easy lookup
|
|
1148
1167
|
const stepNameToId = new Map();
|
|
1149
|
-
if (this.
|
|
1150
|
-
for (const step of this.
|
|
1168
|
+
if (this.Spec.Steps) {
|
|
1169
|
+
for (const step of this.Spec.Steps) {
|
|
1151
1170
|
if (step.ID) {
|
|
1152
1171
|
stepNameToId.set(step.Name, step.ID);
|
|
1153
1172
|
}
|
|
1154
1173
|
}
|
|
1155
1174
|
}
|
|
1156
|
-
for (const pathSpec of this.
|
|
1175
|
+
for (const pathSpec of this.Spec.Paths) {
|
|
1157
1176
|
const pathEntity = await md.GetEntityObject('MJ: AI Agent Step Paths', this._contextUser);
|
|
1158
1177
|
// Load existing if ID provided
|
|
1159
1178
|
const isUpdate = !!(pathSpec.ID && pathSpec.ID !== '');
|
|
@@ -1188,7 +1207,7 @@ export class AgentSpecSync {
|
|
|
1188
1207
|
pathSpec.ID = pathEntity.ID;
|
|
1189
1208
|
console.log(`✅ saveStepPaths: Created AIAgentStepPath with ID: ${pathEntity.ID}`);
|
|
1190
1209
|
}
|
|
1191
|
-
console.log(`✅ saveStepPaths: Successfully saved all ${this.
|
|
1210
|
+
console.log(`✅ saveStepPaths: Successfully saved all ${this.Spec.Paths.length} path(s)`);
|
|
1192
1211
|
}
|
|
1193
1212
|
// ===== UTILITY METHODS =====
|
|
1194
1213
|
/**
|
|
@@ -1283,24 +1302,32 @@ export class AgentSpecSync {
|
|
|
1283
1302
|
* ```
|
|
1284
1303
|
*/
|
|
1285
1304
|
toJSON() {
|
|
1286
|
-
return { ...this.
|
|
1305
|
+
return { ...this.Spec };
|
|
1287
1306
|
}
|
|
1288
1307
|
/**
|
|
1289
1308
|
* Check if this spec has unsaved changes.
|
|
1290
1309
|
*
|
|
1291
1310
|
* @returns True if there are unsaved changes, false otherwise
|
|
1292
1311
|
*/
|
|
1293
|
-
get
|
|
1312
|
+
get IsDirty() {
|
|
1294
1313
|
return this._isDirty;
|
|
1295
1314
|
}
|
|
1315
|
+
/** @deprecated Use {@link IsDirty}. */
|
|
1316
|
+
get isDirty() {
|
|
1317
|
+
return this.IsDirty;
|
|
1318
|
+
}
|
|
1296
1319
|
/**
|
|
1297
1320
|
* Check if this spec has been loaded from the database.
|
|
1298
1321
|
*
|
|
1299
1322
|
* @returns True if loaded from database, false if created in memory
|
|
1300
1323
|
*/
|
|
1301
|
-
get
|
|
1324
|
+
get IsLoaded() {
|
|
1302
1325
|
return this._isLoaded;
|
|
1303
1326
|
}
|
|
1327
|
+
/** @deprecated Use {@link IsLoaded}. */
|
|
1328
|
+
get isLoaded() {
|
|
1329
|
+
return this.IsLoaded;
|
|
1330
|
+
}
|
|
1304
1331
|
/**
|
|
1305
1332
|
* Mark the spec as having changes.
|
|
1306
1333
|
*
|
|
@@ -1316,9 +1343,13 @@ export class AgentSpecSync {
|
|
|
1316
1343
|
* await spec.SaveToDatabase();
|
|
1317
1344
|
* ```
|
|
1318
1345
|
*/
|
|
1319
|
-
|
|
1346
|
+
MarkDirty() {
|
|
1320
1347
|
this._isDirty = true;
|
|
1321
1348
|
}
|
|
1349
|
+
/** @deprecated Use {@link MarkDirty}. */
|
|
1350
|
+
markDirty() {
|
|
1351
|
+
return this.MarkDirty();
|
|
1352
|
+
}
|
|
1322
1353
|
/**
|
|
1323
1354
|
* Mark the spec as having been loaded from the database.
|
|
1324
1355
|
*
|
|
@@ -1336,9 +1367,13 @@ export class AgentSpecSync {
|
|
|
1336
1367
|
* await childSync.SaveToDatabase();
|
|
1337
1368
|
* ```
|
|
1338
1369
|
*/
|
|
1339
|
-
|
|
1370
|
+
MarkLoaded() {
|
|
1340
1371
|
this._isLoaded = true;
|
|
1341
1372
|
}
|
|
1373
|
+
/** @deprecated Use {@link MarkLoaded}. */
|
|
1374
|
+
markLoaded() {
|
|
1375
|
+
return this.MarkLoaded();
|
|
1376
|
+
}
|
|
1342
1377
|
// ===== DELETE/ORPHAN METHODS =====
|
|
1343
1378
|
/**
|
|
1344
1379
|
* Load the current state of all agent-related records from the database.
|