@memberjunction/server 2.117.0 → 2.119.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.
@@ -1,7 +1,7 @@
1
1
  import { Resolver, Mutation, Query, Arg, Ctx, ObjectType, Field, PubSub, PubSubEngine, Subscription, Root, ResolverFilterData, ID } from 'type-graphql';
2
2
  import { AppContext, UserPayload } from '../types.js';
3
- import { DatabaseProviderBase, LogError, LogStatus, Metadata, RunView, UserInfo } from '@memberjunction/core';
4
- import { AIAgentEntityExtended, ArtifactEntity, ArtifactVersionEntity, ConversationDetailArtifactEntity, ConversationDetailEntity, UserNotificationEntity, AIAgentRunEntityExtended } from '@memberjunction/core-entities';
3
+ import { DatabaseProviderBase, LogError, LogStatus, Metadata, UserInfo } from '@memberjunction/core';
4
+ import { AIAgentEntityExtended, ConversationDetailEntity, UserNotificationEntity, AIAgentRunEntityExtended } from '@memberjunction/core-entities';
5
5
  import { AgentRunner } from '@memberjunction/ai-agents';
6
6
  import { ExecuteAgentResult } from '@memberjunction/ai-core-plus';
7
7
  import { AIEngine } from '@memberjunction/aiengine';
@@ -31,8 +31,8 @@ export class AgentExecutionProgress {
31
31
  @Field()
32
32
  currentStep: string;
33
33
 
34
- @Field()
35
- percentage: number;
34
+ @Field({ nullable: true })
35
+ percentage?: number;
36
36
 
37
37
  @Field()
38
38
  message: string;
@@ -42,6 +42,12 @@ export class AgentExecutionProgress {
42
42
 
43
43
  @Field({ nullable: true })
44
44
  agentType?: string;
45
+
46
+ @Field({ nullable: true })
47
+ stepCount?: number;
48
+
49
+ @Field({ nullable: true })
50
+ hierarchicalStep?: string;
45
51
  }
46
52
 
47
53
  @ObjectType()
@@ -136,7 +142,9 @@ export class RunAIAgentResolver extends ResolverBase {
136
142
  const sanitized: any = {
137
143
  success: result.success,
138
144
  payload: result.payload,
139
- suggestedResponses: result.suggestedResponses,
145
+ responseForm: result.responseForm,
146
+ actionableCommands: result.actionableCommands,
147
+ automaticCommands: result.automaticCommands,
140
148
  errorMessage: result.agentRun?.ErrorMessage,
141
149
  finalStep: result.agentRun?.FinalStep,
142
150
  cancelled: result.agentRun?.Status === 'Cancelled',
@@ -230,7 +238,9 @@ export class RunAIAgentResolver extends ResolverBase {
230
238
  percentage: progress.percentage,
231
239
  message: progress.message,
232
240
  agentName: (progress.metadata as any)?.agentName || undefined,
233
- agentType: (progress.metadata as any)?.agentType || undefined
241
+ agentType: (progress.metadata as any)?.agentType || undefined,
242
+ stepCount: (progress.metadata as any)?.stepCount || undefined,
243
+ hierarchicalStep: (progress.metadata as any)?.hierarchicalStep || undefined
234
244
  },
235
245
  timestamp: new Date()
236
246
  };
@@ -354,14 +364,14 @@ export class RunAIAgentResolver extends ResolverBase {
354
364
  // for multi-user server environments like this one
355
365
  // Create AI agent runner
356
366
  const agentRunner = new AgentRunner();
357
-
367
+
358
368
  // Track agent run for streaming (use ref to update later)
359
369
  const agentRunRef = { current: null as any };
360
370
 
361
371
  console.log(`🚀 Starting agent execution with sessionId: ${sessionId}`);
362
372
 
363
- // Execute the agent with streaming callbacks
364
- const result = await agentRunner.RunAgent({
373
+ // Execute the agent in conversation context - handles conversation, artifacts, etc.
374
+ const conversationResult = await agentRunner.RunAgentInConversation({
365
375
  agent: agentEntity,
366
376
  conversationMessages: parsedMessages,
367
377
  payload: payload ? SafeJSONParse(payload) : undefined,
@@ -372,12 +382,20 @@ export class RunAIAgentResolver extends ResolverBase {
372
382
  autoPopulateLastRunPayload: autoPopulateLastRunPayload,
373
383
  configurationId: configurationId,
374
384
  data: parsedData,
375
- conversationDetailId: conversationDetailId,
376
385
  context: {
377
386
  dataSource: dataSource
378
387
  }
388
+ }, {
389
+ conversationDetailId: conversationDetailId, // Use existing if provided
390
+ createArtifacts: createArtifacts || false,
391
+ sourceArtifactId: sourceArtifactId
379
392
  });
380
393
 
394
+ const result = conversationResult.agentResult;
395
+ // Use agent response detail ID if available, otherwise fall back to user message detail ID
396
+ const finalConversationDetailId = conversationResult.agentResponseDetailId || conversationResult.userMessageDetailId;
397
+ const artifactInfo = conversationResult.artifactInfo;
398
+
381
399
  // Update agent run ref once available
382
400
  if (result.agentRun) {
383
401
  agentRunRef.current = result.agentRun;
@@ -388,35 +406,20 @@ export class RunAIAgentResolver extends ResolverBase {
388
406
  // Publish final events
389
407
  this.publishFinalEvents(pubSub, sessionId, userPayload, result);
390
408
 
391
- // Process completion for artifacts and notifications (if enabled)
392
- if (result.success && conversationDetailId && result.payload) {
393
- const currentUser = this.GetUserFromPayload(userPayload);
394
-
395
- if (createArtifacts) {
396
- const artifactInfo = await this.processAgentCompletionForArtifacts(
397
- result.agentRun,
398
- result.payload,
399
- currentUser,
400
- conversationDetailId,
401
- sourceArtifactId
402
- );
403
-
404
- // Create notification if enabled and artifact was created successfully
405
- if (createNotification && artifactInfo.artifactId && artifactInfo.versionId && artifactInfo.versionNumber) {
406
- await this.createCompletionNotification(
407
- result.agentRun,
408
- {
409
- artifactId: artifactInfo.artifactId,
410
- versionId: artifactInfo.versionId,
411
- versionNumber: artifactInfo.versionNumber
412
- },
413
- conversationDetailId,
414
- currentUser,
415
- pubSub,
416
- userPayload
417
- );
418
- }
419
- }
409
+ // Create notification if enabled and artifact was created successfully
410
+ if (createNotification && result.success && artifactInfo && artifactInfo.artifactId && artifactInfo.versionId && artifactInfo.versionNumber) {
411
+ await this.createCompletionNotification(
412
+ result.agentRun,
413
+ {
414
+ artifactId: artifactInfo.artifactId,
415
+ versionId: artifactInfo.versionId,
416
+ versionNumber: artifactInfo.versionNumber
417
+ },
418
+ finalConversationDetailId,
419
+ currentUser,
420
+ pubSub,
421
+ userPayload
422
+ );
420
423
  }
421
424
 
422
425
  // Create sanitized payload for JSON serialization
@@ -588,259 +591,6 @@ export class RunAIAgentResolver extends ResolverBase {
588
591
  );
589
592
  }
590
593
 
591
- /**
592
- * Get the maximum version number for an artifact
593
- * Used when creating new version of an explicitly specified artifact
594
- */
595
- private async getMaxVersionForArtifact(
596
- artifactId: string,
597
- contextUser: UserInfo
598
- ): Promise<number> {
599
- try {
600
- const rv = new RunView();
601
-
602
- // Query all versions for this artifact to find max version number
603
- const result = await rv.RunView<ArtifactVersionEntity>({
604
- EntityName: 'MJ: Artifact Versions',
605
- ExtraFilter: `ArtifactID='${artifactId}'`,
606
- OrderBy: 'VersionNumber DESC',
607
- MaxRows: 1,
608
- ResultType: 'entity_object'
609
- }, contextUser);
610
-
611
- if (result.Success && result.Results && result.Results.length > 0) {
612
- return result.Results[0].VersionNumber || 0;
613
- }
614
-
615
- return 0; // No versions found, will create version 1
616
- } catch (error) {
617
- LogError(`Error getting max version for artifact: ${(error as Error).message}`);
618
- return 0;
619
- }
620
- }
621
-
622
- /**
623
- * Find the most recent artifact for a conversation detail to determine versioning
624
- * Returns artifact info if exists, null if this is first artifact
625
- */
626
- private async findPreviousArtifactForMessage(
627
- conversationDetailId: string,
628
- contextUser: UserInfo
629
- ): Promise<{ artifactId: string; versionNumber: number } | null> {
630
- try {
631
- const rv = new RunView();
632
-
633
- // Query junction table to find artifacts for this message
634
- const result = await rv.RunView<ConversationDetailArtifactEntity>({
635
- EntityName: 'MJ: Conversation Detail Artifacts',
636
- ExtraFilter: `ConversationDetailID='${conversationDetailId}' AND Direction='Output'`,
637
- OrderBy: '__mj_CreatedAt DESC',
638
- MaxRows: 1,
639
- ResultType: 'entity_object'
640
- }, contextUser);
641
-
642
- if (!result.Success || !result.Results || result.Results.length === 0) {
643
- return null;
644
- }
645
-
646
- const junction = result.Results[0];
647
-
648
- // Load the artifact version to get version number and artifact ID
649
- const md = new Metadata();
650
- const version = await md.GetEntityObject<ArtifactVersionEntity>(
651
- 'MJ: Artifact Versions',
652
- contextUser
653
- );
654
-
655
- if (!(await version.Load(junction.ArtifactVersionID))) {
656
- return null;
657
- }
658
-
659
- return {
660
- artifactId: version.ArtifactID,
661
- versionNumber: version.VersionNumber
662
- };
663
- } catch (error) {
664
- LogError(`Error finding previous artifact: ${(error as Error).message}`);
665
- return null;
666
- }
667
- }
668
-
669
- /**
670
- * Process agent completion to create artifacts from payload
671
- * Called after agent run completes successfully
672
- */
673
- private async processAgentCompletionForArtifacts(
674
- agentRun: AIAgentRunEntityExtended,
675
- payload: any,
676
- contextUser: UserInfo,
677
- conversationDetailId?: string,
678
- sourceArtifactId?: string
679
- ): Promise<{ artifactId?: string; versionId?: string; versionNumber?: number }> {
680
- // Validate inputs
681
- if (!payload || Object.keys(payload).length === 0) {
682
- LogStatus('No payload to create artifact from');
683
- return {};
684
- }
685
-
686
- if (!conversationDetailId) {
687
- LogStatus('Skipping artifact creation - no conversationDetailId provided');
688
- return {};
689
- }
690
-
691
- // Check agent's ArtifactCreationMode
692
- await AIEngine.Instance.Config(false, contextUser);
693
- const agent = AIEngine.Instance.Agents.find(a => a.ID === agentRun.AgentID);
694
- const creationMode = agent?.ArtifactCreationMode;
695
-
696
- if (creationMode === 'Never') {
697
- LogStatus(`Skipping artifact creation - agent "${agent?.Name}" has ArtifactCreationMode='Never'`);
698
- return {};
699
- }
700
-
701
- try {
702
- const md = new Metadata();
703
- const JSON_ARTIFACT_TYPE_ID = 'ae674c7e-ea0d-49ea-89e4-0649f5eb20d4';
704
-
705
- // 1. Determine if creating new artifact or new version
706
- let artifactId: string;
707
- let newVersionNumber: number;
708
- let isNewArtifact = false;
709
-
710
- // Priority 1: Use explicit source artifact if provided (agent continuity/refinement)
711
- if (sourceArtifactId) {
712
- const maxVersion = await this.getMaxVersionForArtifact(sourceArtifactId, contextUser);
713
- artifactId = sourceArtifactId;
714
- newVersionNumber = maxVersion + 1;
715
- LogStatus(`Creating version ${newVersionNumber} of source artifact ${artifactId} (explicit source)`);
716
- }
717
- // Priority 2: Try to find previous artifact for this message (fallback)
718
- else {
719
- const previousArtifact = await this.findPreviousArtifactForMessage(
720
- conversationDetailId,
721
- contextUser
722
- );
723
-
724
- if (previousArtifact) {
725
- // Create new version of existing artifact
726
- artifactId = previousArtifact.artifactId;
727
- newVersionNumber = previousArtifact.versionNumber + 1;
728
- LogStatus(`Creating version ${newVersionNumber} of existing artifact ${artifactId}`);
729
- } else {
730
- // Create new artifact header
731
- const artifact = await md.GetEntityObject<ArtifactEntity>(
732
- 'MJ: Artifacts',
733
- contextUser
734
- );
735
-
736
- // Get agent info for naming and visibility control
737
- await AIEngine.Instance.Config(false, contextUser);
738
- const agent = AIEngine.Instance.Agents.find(a => a.ID === agentRun.AgentID);
739
- const agentName = agent?.Name || 'Agent';
740
-
741
- artifact.Name = `${agentName} Payload - ${new Date().toLocaleString()}`;
742
- artifact.Description = `Payload returned by ${agentName}`;
743
-
744
- // Use agent's DefaultArtifactTypeID if available, otherwise JSON
745
- const defaultArtifactTypeId = (agent as any)?.DefaultArtifactTypeID;
746
- artifact.TypeID = defaultArtifactTypeId || JSON_ARTIFACT_TYPE_ID;
747
-
748
- artifact.UserID = contextUser.ID;
749
- artifact.EnvironmentID = (contextUser as any).EnvironmentID ||
750
- 'F51358F3-9447-4176-B313-BF8025FD8D09';
751
-
752
- // Set visibility based on agent's ArtifactCreationMode
753
- // Will compile after CodeGen adds the new fields
754
- const creationMode = agent.ArtifactCreationMode;
755
- if (creationMode === 'System Only') {
756
- artifact.Visibility = 'System Only';
757
- LogStatus(`Artifact marked as "System Only" per agent configuration`);
758
- } else {
759
- artifact.Visibility = 'Always';
760
- }
761
-
762
- if (!(await artifact.Save())) {
763
- throw new Error('Failed to save artifact');
764
- }
765
-
766
- artifactId = artifact.ID;
767
- newVersionNumber = 1;
768
- isNewArtifact = true;
769
- LogStatus(`Created new artifact: ${artifact.Name} (${artifactId})`);
770
- }
771
- }
772
-
773
- // 2. Create artifact version with content
774
- const version = await md.GetEntityObject<ArtifactVersionEntity>(
775
- 'MJ: Artifact Versions',
776
- contextUser
777
- );
778
- version.ArtifactID = artifactId;
779
- version.VersionNumber = newVersionNumber;
780
- version.Content = JSON.stringify(payload, null, 2);
781
- version.UserID = contextUser.ID;
782
-
783
- if (!(await version.Save())) {
784
- throw new Error('Failed to save artifact version');
785
- }
786
-
787
- LogStatus(`Created artifact version ${newVersionNumber} (${version.ID})`);
788
-
789
- // If this is the first version of a new artifact, check for extracted Name attribute and update artifact
790
- if (isNewArtifact && newVersionNumber === 1) {
791
- const nameAttr = (version as any).Attributes?.find((attr: any) =>
792
- attr.StandardProperty === 'name' || attr.Name?.toLowerCase() === 'name'
793
- );
794
-
795
- // Check for valid name value (not null, not empty, not string "null")
796
- let extractedName = nameAttr?.Value?.trim();
797
- if (extractedName && extractedName.toLowerCase() !== 'null') {
798
- // Strip surrounding quotes (double or single) from start and end
799
- extractedName = extractedName.replace(/^["']|["']$/g, '');
800
-
801
- // Load artifact to update with extracted name
802
- const artifact = await md.GetEntityObject<ArtifactEntity>(
803
- 'MJ: Artifacts',
804
- contextUser
805
- );
806
-
807
- if (!(await artifact.Load(artifactId))) {
808
- LogError('Failed to reload artifact for name update');
809
- } else {
810
- artifact.Name = extractedName;
811
- if (await artifact.Save()) {
812
- LogStatus(`✨ Updated artifact name to: ${artifact.Name}`);
813
- }
814
- }
815
- }
816
- }
817
-
818
- // 3. Create junction record linking artifact to conversation detail
819
- const junction = await md.GetEntityObject<ConversationDetailArtifactEntity>(
820
- 'MJ: Conversation Detail Artifacts',
821
- contextUser
822
- );
823
- junction.ConversationDetailID = conversationDetailId;
824
- junction.ArtifactVersionID = version.ID;
825
- junction.Direction = 'Output';
826
-
827
- if (!(await junction.Save())) {
828
- throw new Error('Failed to create artifact-message association');
829
- }
830
-
831
- LogStatus(`Linked artifact to conversation detail ${conversationDetailId}`);
832
-
833
- return {
834
- artifactId,
835
- versionId: version.ID,
836
- versionNumber: newVersionNumber
837
- };
838
- } catch (error) {
839
- LogError(`Failed to process agent completion for artifacts: ${(error as Error).message}`);
840
- return {};
841
- }
842
- }
843
-
844
594
  /**
845
595
  * Create a user notification for agent completion with artifact
846
596
  * Notification includes navigation link back to the conversation