@memberjunction/server 2.109.0 → 2.110.1
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/dist/agents/skip-agent.d.ts +1 -0
- package/dist/agents/skip-agent.d.ts.map +1 -1
- package/dist/agents/skip-agent.js +65 -7
- package/dist/agents/skip-agent.js.map +1 -1
- package/dist/agents/skip-sdk.d.ts.map +1 -1
- package/dist/agents/skip-sdk.js +29 -14
- package/dist/agents/skip-sdk.js.map +1 -1
- package/dist/generated/generated.d.ts +34 -13
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +194 -86
- package/dist/generated/generated.js.map +1 -1
- package/dist/resolvers/CreateQueryResolver.d.ts +1 -0
- package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/CreateQueryResolver.js +73 -11
- package/dist/resolvers/CreateQueryResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts +6 -2
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +234 -8
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/TaskResolver.d.ts +1 -1
- package/dist/resolvers/TaskResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskResolver.js +4 -3
- package/dist/resolvers/TaskResolver.js.map +1 -1
- package/dist/services/TaskOrchestrator.d.ts +3 -1
- package/dist/services/TaskOrchestrator.d.ts.map +1 -1
- package/dist/services/TaskOrchestrator.js +77 -2
- package/dist/services/TaskOrchestrator.js.map +1 -1
- package/package.json +35 -35
- package/src/agents/skip-agent.ts +93 -9
- package/src/agents/skip-sdk.ts +45 -16
- package/src/generated/generated.ts +135 -69
- package/src/resolvers/CreateQueryResolver.ts +125 -28
- package/src/resolvers/RunAIAgentResolver.ts +397 -9
- package/src/resolvers/TaskResolver.ts +3 -2
- package/src/services/TaskOrchestrator.ts +118 -3
|
@@ -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 } from '@memberjunction/core';
|
|
4
|
-
import { AIAgentEntityExtended } from '@memberjunction/core-entities';
|
|
3
|
+
import { DatabaseProviderBase, LogError, LogStatus, Metadata, RunView, UserInfo } from '@memberjunction/core';
|
|
4
|
+
import { AIAgentEntityExtended, ArtifactEntity, ArtifactVersionEntity, ConversationDetailArtifactEntity, 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';
|
|
@@ -314,12 +314,16 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
314
314
|
sessionId: string,
|
|
315
315
|
pubSub: PubSubEngine,
|
|
316
316
|
data?: string,
|
|
317
|
-
payload?: string,
|
|
317
|
+
payload?: string,
|
|
318
318
|
templateData?: string,
|
|
319
319
|
lastRunId?: string,
|
|
320
320
|
autoPopulateLastRunPayload?: boolean,
|
|
321
321
|
configurationId?: string,
|
|
322
|
-
conversationDetailId?: string
|
|
322
|
+
conversationDetailId?: string,
|
|
323
|
+
createArtifacts: boolean = false,
|
|
324
|
+
createNotification: boolean = false,
|
|
325
|
+
sourceArtifactId?: string,
|
|
326
|
+
sourceArtifactVersionId?: string
|
|
323
327
|
): Promise<AIAgentRunResult> {
|
|
324
328
|
const startTime = Date.now();
|
|
325
329
|
|
|
@@ -380,6 +384,37 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
380
384
|
// Publish final events
|
|
381
385
|
this.publishFinalEvents(pubSub, sessionId, userPayload, result);
|
|
382
386
|
|
|
387
|
+
// Process completion for artifacts and notifications (if enabled)
|
|
388
|
+
if (result.success && conversationDetailId && result.payload) {
|
|
389
|
+
const currentUser = this.GetUserFromPayload(userPayload);
|
|
390
|
+
|
|
391
|
+
if (createArtifacts) {
|
|
392
|
+
const artifactInfo = await this.processAgentCompletionForArtifacts(
|
|
393
|
+
result.agentRun,
|
|
394
|
+
result.payload,
|
|
395
|
+
currentUser,
|
|
396
|
+
conversationDetailId,
|
|
397
|
+
sourceArtifactId
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
// Create notification if enabled and artifact was created successfully
|
|
401
|
+
if (createNotification && artifactInfo.artifactId && artifactInfo.versionId && artifactInfo.versionNumber) {
|
|
402
|
+
await this.createCompletionNotification(
|
|
403
|
+
result.agentRun,
|
|
404
|
+
{
|
|
405
|
+
artifactId: artifactInfo.artifactId,
|
|
406
|
+
versionId: artifactInfo.versionId,
|
|
407
|
+
versionNumber: artifactInfo.versionNumber
|
|
408
|
+
},
|
|
409
|
+
conversationDetailId,
|
|
410
|
+
currentUser,
|
|
411
|
+
pubSub,
|
|
412
|
+
userPayload
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
383
418
|
// Create sanitized payload for JSON serialization
|
|
384
419
|
const sanitizedResult = this.sanitizeAgentResult(result);
|
|
385
420
|
const returnResult = JSON.stringify(sanitizedResult);
|
|
@@ -473,7 +508,11 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
473
508
|
@Arg('lastRunId', { nullable: true }) lastRunId?: string,
|
|
474
509
|
@Arg('autoPopulateLastRunPayload', { nullable: true }) autoPopulateLastRunPayload?: boolean,
|
|
475
510
|
@Arg('configurationId', { nullable: true }) configurationId?: string,
|
|
476
|
-
@Arg('conversationDetailId', { nullable: true }) conversationDetailId?: string
|
|
511
|
+
@Arg('conversationDetailId', { nullable: true }) conversationDetailId?: string,
|
|
512
|
+
@Arg('createArtifacts', { nullable: true }) createArtifacts?: boolean,
|
|
513
|
+
@Arg('createNotification', { nullable: true }) createNotification?: boolean,
|
|
514
|
+
@Arg('sourceArtifactId', { nullable: true }) sourceArtifactId?: string,
|
|
515
|
+
@Arg('sourceArtifactVersionId', { nullable: true }) sourceArtifactVersionId?: string
|
|
477
516
|
): Promise<AIAgentRunResult> {
|
|
478
517
|
const p = GetReadWriteProvider(providers);
|
|
479
518
|
return this.executeAIAgent(
|
|
@@ -489,7 +528,11 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
489
528
|
lastRunId,
|
|
490
529
|
autoPopulateLastRunPayload,
|
|
491
530
|
configurationId,
|
|
492
|
-
conversationDetailId
|
|
531
|
+
conversationDetailId,
|
|
532
|
+
createArtifacts || false,
|
|
533
|
+
createNotification || false,
|
|
534
|
+
sourceArtifactId,
|
|
535
|
+
sourceArtifactVersionId
|
|
493
536
|
);
|
|
494
537
|
}
|
|
495
538
|
|
|
@@ -511,7 +554,11 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
511
554
|
@Arg('lastRunId', { nullable: true }) lastRunId?: string,
|
|
512
555
|
@Arg('autoPopulateLastRunPayload', { nullable: true }) autoPopulateLastRunPayload?: boolean,
|
|
513
556
|
@Arg('configurationId', { nullable: true }) configurationId?: string,
|
|
514
|
-
@Arg('conversationDetailId', { nullable: true }) conversationDetailId?: string
|
|
557
|
+
@Arg('conversationDetailId', { nullable: true }) conversationDetailId?: string,
|
|
558
|
+
@Arg('createArtifacts', { nullable: true }) createArtifacts?: boolean,
|
|
559
|
+
@Arg('createNotification', { nullable: true }) createNotification?: boolean,
|
|
560
|
+
@Arg('sourceArtifactId', { nullable: true }) sourceArtifactId?: string,
|
|
561
|
+
@Arg('sourceArtifactVersionId', { nullable: true }) sourceArtifactVersionId?: string
|
|
515
562
|
): Promise<AIAgentRunResult> {
|
|
516
563
|
const p = GetReadWriteProvider(providers);
|
|
517
564
|
return this.executeAIAgent(
|
|
@@ -527,8 +574,349 @@ export class RunAIAgentResolver extends ResolverBase {
|
|
|
527
574
|
lastRunId,
|
|
528
575
|
autoPopulateLastRunPayload,
|
|
529
576
|
configurationId,
|
|
530
|
-
conversationDetailId
|
|
577
|
+
conversationDetailId,
|
|
578
|
+
createArtifacts || false,
|
|
579
|
+
createNotification || false,
|
|
580
|
+
sourceArtifactId,
|
|
581
|
+
sourceArtifactVersionId
|
|
531
582
|
);
|
|
532
583
|
}
|
|
533
|
-
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Get the maximum version number for an artifact
|
|
587
|
+
* Used when creating new version of an explicitly specified artifact
|
|
588
|
+
*/
|
|
589
|
+
private async getMaxVersionForArtifact(
|
|
590
|
+
artifactId: string,
|
|
591
|
+
contextUser: UserInfo
|
|
592
|
+
): Promise<number> {
|
|
593
|
+
try {
|
|
594
|
+
const rv = new RunView();
|
|
595
|
+
|
|
596
|
+
// Query all versions for this artifact to find max version number
|
|
597
|
+
const result = await rv.RunView<ArtifactVersionEntity>({
|
|
598
|
+
EntityName: 'MJ: Artifact Versions',
|
|
599
|
+
ExtraFilter: `ArtifactID='${artifactId}'`,
|
|
600
|
+
OrderBy: 'VersionNumber DESC',
|
|
601
|
+
MaxRows: 1,
|
|
602
|
+
ResultType: 'entity_object'
|
|
603
|
+
}, contextUser);
|
|
604
|
+
|
|
605
|
+
if (result.Success && result.Results && result.Results.length > 0) {
|
|
606
|
+
return result.Results[0].VersionNumber || 0;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
return 0; // No versions found, will create version 1
|
|
610
|
+
} catch (error) {
|
|
611
|
+
LogError(`Error getting max version for artifact: ${(error as Error).message}`);
|
|
612
|
+
return 0;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Find the most recent artifact for a conversation detail to determine versioning
|
|
618
|
+
* Returns artifact info if exists, null if this is first artifact
|
|
619
|
+
*/
|
|
620
|
+
private async findPreviousArtifactForMessage(
|
|
621
|
+
conversationDetailId: string,
|
|
622
|
+
contextUser: UserInfo
|
|
623
|
+
): Promise<{ artifactId: string; versionNumber: number } | null> {
|
|
624
|
+
try {
|
|
625
|
+
const rv = new RunView();
|
|
626
|
+
|
|
627
|
+
// Query junction table to find artifacts for this message
|
|
628
|
+
const result = await rv.RunView<ConversationDetailArtifactEntity>({
|
|
629
|
+
EntityName: 'MJ: Conversation Detail Artifacts',
|
|
630
|
+
ExtraFilter: `ConversationDetailID='${conversationDetailId}' AND Direction='Output'`,
|
|
631
|
+
OrderBy: '__mj_CreatedAt DESC',
|
|
632
|
+
MaxRows: 1,
|
|
633
|
+
ResultType: 'entity_object'
|
|
634
|
+
}, contextUser);
|
|
635
|
+
|
|
636
|
+
if (!result.Success || !result.Results || result.Results.length === 0) {
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const junction = result.Results[0];
|
|
641
|
+
|
|
642
|
+
// Load the artifact version to get version number and artifact ID
|
|
643
|
+
const md = new Metadata();
|
|
644
|
+
const version = await md.GetEntityObject<ArtifactVersionEntity>(
|
|
645
|
+
'MJ: Artifact Versions',
|
|
646
|
+
contextUser
|
|
647
|
+
);
|
|
648
|
+
|
|
649
|
+
if (!(await version.Load(junction.ArtifactVersionID))) {
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return {
|
|
654
|
+
artifactId: version.ArtifactID,
|
|
655
|
+
versionNumber: version.VersionNumber
|
|
656
|
+
};
|
|
657
|
+
} catch (error) {
|
|
658
|
+
LogError(`Error finding previous artifact: ${(error as Error).message}`);
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Process agent completion to create artifacts from payload
|
|
665
|
+
* Called after agent run completes successfully
|
|
666
|
+
*/
|
|
667
|
+
private async processAgentCompletionForArtifacts(
|
|
668
|
+
agentRun: AIAgentRunEntityExtended,
|
|
669
|
+
payload: any,
|
|
670
|
+
contextUser: UserInfo,
|
|
671
|
+
conversationDetailId?: string,
|
|
672
|
+
sourceArtifactId?: string
|
|
673
|
+
): Promise<{ artifactId?: string; versionId?: string; versionNumber?: number }> {
|
|
674
|
+
// Validate inputs
|
|
675
|
+
if (!payload || Object.keys(payload).length === 0) {
|
|
676
|
+
LogStatus('No payload to create artifact from');
|
|
677
|
+
return {};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (!conversationDetailId) {
|
|
681
|
+
LogStatus('Skipping artifact creation - no conversationDetailId provided');
|
|
682
|
+
return {};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Check agent's ArtifactCreationMode
|
|
686
|
+
await AIEngine.Instance.Config(false, contextUser);
|
|
687
|
+
const agent = AIEngine.Instance.Agents.find(a => a.ID === agentRun.AgentID);
|
|
688
|
+
const creationMode = agent?.ArtifactCreationMode;
|
|
689
|
+
|
|
690
|
+
if (creationMode === 'Never') {
|
|
691
|
+
LogStatus(`Skipping artifact creation - agent "${agent?.Name}" has ArtifactCreationMode='Never'`);
|
|
692
|
+
return {};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
try {
|
|
696
|
+
const md = new Metadata();
|
|
697
|
+
const JSON_ARTIFACT_TYPE_ID = 'ae674c7e-ea0d-49ea-89e4-0649f5eb20d4';
|
|
698
|
+
|
|
699
|
+
// 1. Determine if creating new artifact or new version
|
|
700
|
+
let artifactId: string;
|
|
701
|
+
let newVersionNumber: number;
|
|
702
|
+
let isNewArtifact = false;
|
|
703
|
+
|
|
704
|
+
// Priority 1: Use explicit source artifact if provided (agent continuity/refinement)
|
|
705
|
+
if (sourceArtifactId) {
|
|
706
|
+
const maxVersion = await this.getMaxVersionForArtifact(sourceArtifactId, contextUser);
|
|
707
|
+
artifactId = sourceArtifactId;
|
|
708
|
+
newVersionNumber = maxVersion + 1;
|
|
709
|
+
LogStatus(`Creating version ${newVersionNumber} of source artifact ${artifactId} (explicit source)`);
|
|
710
|
+
}
|
|
711
|
+
// Priority 2: Try to find previous artifact for this message (fallback)
|
|
712
|
+
else {
|
|
713
|
+
const previousArtifact = await this.findPreviousArtifactForMessage(
|
|
714
|
+
conversationDetailId,
|
|
715
|
+
contextUser
|
|
716
|
+
);
|
|
717
|
+
|
|
718
|
+
if (previousArtifact) {
|
|
719
|
+
// Create new version of existing artifact
|
|
720
|
+
artifactId = previousArtifact.artifactId;
|
|
721
|
+
newVersionNumber = previousArtifact.versionNumber + 1;
|
|
722
|
+
LogStatus(`Creating version ${newVersionNumber} of existing artifact ${artifactId}`);
|
|
723
|
+
} else {
|
|
724
|
+
// Create new artifact header
|
|
725
|
+
const artifact = await md.GetEntityObject<ArtifactEntity>(
|
|
726
|
+
'MJ: Artifacts',
|
|
727
|
+
contextUser
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
// Get agent info for naming and visibility control
|
|
731
|
+
await AIEngine.Instance.Config(false, contextUser);
|
|
732
|
+
const agent = AIEngine.Instance.Agents.find(a => a.ID === agentRun.AgentID);
|
|
733
|
+
const agentName = agent?.Name || 'Agent';
|
|
734
|
+
|
|
735
|
+
artifact.Name = `${agentName} Payload - ${new Date().toLocaleString()}`;
|
|
736
|
+
artifact.Description = `Payload returned by ${agentName}`;
|
|
737
|
+
|
|
738
|
+
// Use agent's DefaultArtifactTypeID if available, otherwise JSON
|
|
739
|
+
const defaultArtifactTypeId = (agent as any)?.DefaultArtifactTypeID;
|
|
740
|
+
artifact.TypeID = defaultArtifactTypeId || JSON_ARTIFACT_TYPE_ID;
|
|
741
|
+
|
|
742
|
+
artifact.UserID = contextUser.ID;
|
|
743
|
+
artifact.EnvironmentID = (contextUser as any).EnvironmentID ||
|
|
744
|
+
'F51358F3-9447-4176-B313-BF8025FD8D09';
|
|
745
|
+
|
|
746
|
+
// Set visibility based on agent's ArtifactCreationMode
|
|
747
|
+
// Will compile after CodeGen adds the new fields
|
|
748
|
+
const creationMode = agent.ArtifactCreationMode;
|
|
749
|
+
if (creationMode === 'System Only') {
|
|
750
|
+
artifact.Visibility = 'System Only';
|
|
751
|
+
LogStatus(`Artifact marked as "System Only" per agent configuration`);
|
|
752
|
+
} else {
|
|
753
|
+
artifact.Visibility = 'Always';
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (!(await artifact.Save())) {
|
|
757
|
+
throw new Error('Failed to save artifact');
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
artifactId = artifact.ID;
|
|
761
|
+
newVersionNumber = 1;
|
|
762
|
+
isNewArtifact = true;
|
|
763
|
+
LogStatus(`Created new artifact: ${artifact.Name} (${artifactId})`);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// 2. Create artifact version with content
|
|
768
|
+
const version = await md.GetEntityObject<ArtifactVersionEntity>(
|
|
769
|
+
'MJ: Artifact Versions',
|
|
770
|
+
contextUser
|
|
771
|
+
);
|
|
772
|
+
version.ArtifactID = artifactId;
|
|
773
|
+
version.VersionNumber = newVersionNumber;
|
|
774
|
+
version.Content = JSON.stringify(payload, null, 2);
|
|
775
|
+
version.UserID = contextUser.ID;
|
|
776
|
+
|
|
777
|
+
if (!(await version.Save())) {
|
|
778
|
+
throw new Error('Failed to save artifact version');
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
LogStatus(`Created artifact version ${newVersionNumber} (${version.ID})`);
|
|
782
|
+
|
|
783
|
+
// If this is the first version of a new artifact, check for extracted Name attribute and update artifact
|
|
784
|
+
if (isNewArtifact && newVersionNumber === 1) {
|
|
785
|
+
const nameAttr = (version as any).Attributes?.find((attr: any) =>
|
|
786
|
+
attr.StandardProperty === 'name' || attr.Name?.toLowerCase() === 'name'
|
|
787
|
+
);
|
|
788
|
+
|
|
789
|
+
// Check for valid name value (not null, not empty, not string "null")
|
|
790
|
+
let extractedName = nameAttr?.Value?.trim();
|
|
791
|
+
if (extractedName && extractedName.toLowerCase() !== 'null') {
|
|
792
|
+
// Strip surrounding quotes (double or single) from start and end
|
|
793
|
+
extractedName = extractedName.replace(/^["']|["']$/g, '');
|
|
794
|
+
|
|
795
|
+
// Load artifact to update with extracted name
|
|
796
|
+
const artifact = await md.GetEntityObject<ArtifactEntity>(
|
|
797
|
+
'MJ: Artifacts',
|
|
798
|
+
contextUser
|
|
799
|
+
);
|
|
800
|
+
|
|
801
|
+
if (!(await artifact.Load(artifactId))) {
|
|
802
|
+
LogError('Failed to reload artifact for name update');
|
|
803
|
+
} else {
|
|
804
|
+
artifact.Name = extractedName;
|
|
805
|
+
if (await artifact.Save()) {
|
|
806
|
+
LogStatus(`✨ Updated artifact name to: ${artifact.Name}`);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// 3. Create junction record linking artifact to conversation detail
|
|
813
|
+
const junction = await md.GetEntityObject<ConversationDetailArtifactEntity>(
|
|
814
|
+
'MJ: Conversation Detail Artifacts',
|
|
815
|
+
contextUser
|
|
816
|
+
);
|
|
817
|
+
junction.ConversationDetailID = conversationDetailId;
|
|
818
|
+
junction.ArtifactVersionID = version.ID;
|
|
819
|
+
junction.Direction = 'Output';
|
|
820
|
+
|
|
821
|
+
if (!(await junction.Save())) {
|
|
822
|
+
throw new Error('Failed to create artifact-message association');
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
LogStatus(`Linked artifact to conversation detail ${conversationDetailId}`);
|
|
826
|
+
|
|
827
|
+
return {
|
|
828
|
+
artifactId,
|
|
829
|
+
versionId: version.ID,
|
|
830
|
+
versionNumber: newVersionNumber
|
|
831
|
+
};
|
|
832
|
+
} catch (error) {
|
|
833
|
+
LogError(`Failed to process agent completion for artifacts: ${(error as Error).message}`);
|
|
834
|
+
return {};
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Create a user notification for agent completion with artifact
|
|
840
|
+
* Notification includes navigation link back to the conversation
|
|
841
|
+
*/
|
|
842
|
+
private async createCompletionNotification(
|
|
843
|
+
agentRun: AIAgentRunEntityExtended,
|
|
844
|
+
artifactInfo: { artifactId: string; versionId: string; versionNumber: number },
|
|
845
|
+
conversationDetailId: string,
|
|
846
|
+
contextUser: UserInfo,
|
|
847
|
+
pubSub: PubSubEngine,
|
|
848
|
+
userPayload: UserPayload
|
|
849
|
+
): Promise<void> {
|
|
850
|
+
try {
|
|
851
|
+
const md = new Metadata();
|
|
852
|
+
|
|
853
|
+
// Get agent info for notification message
|
|
854
|
+
await AIEngine.Instance.Config(false, contextUser);
|
|
855
|
+
const agent = AIEngine.Instance.Agents.find(a => a.ID === agentRun.AgentID);
|
|
856
|
+
const agentName = agent?.Name || 'Agent';
|
|
857
|
+
|
|
858
|
+
// Load conversation detail to get conversation info
|
|
859
|
+
const detail = await md.GetEntityObject<ConversationDetailEntity>(
|
|
860
|
+
'Conversation Details',
|
|
861
|
+
contextUser
|
|
862
|
+
);
|
|
863
|
+
if (!(await detail.Load(conversationDetailId))) {
|
|
864
|
+
throw new Error(`Failed to load conversation detail ${conversationDetailId}`);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// Create notification entity
|
|
868
|
+
const notification = await md.GetEntityObject<UserNotificationEntity>(
|
|
869
|
+
'User Notifications',
|
|
870
|
+
contextUser
|
|
871
|
+
);
|
|
872
|
+
|
|
873
|
+
notification.UserID = contextUser.ID;
|
|
874
|
+
notification.Title = `${agentName} completed your request`;
|
|
875
|
+
|
|
876
|
+
// Craft message based on versioning
|
|
877
|
+
if (artifactInfo.versionNumber > 1) {
|
|
878
|
+
notification.Message = `${agentName} has finished processing and created version ${artifactInfo.versionNumber}`;
|
|
879
|
+
} else {
|
|
880
|
+
notification.Message = `${agentName} has finished processing and created a new artifact`;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Store navigation configuration as JSON
|
|
884
|
+
// Client will parse this to navigate to the conversation with artifact visible
|
|
885
|
+
notification.ResourceConfiguration = JSON.stringify({
|
|
886
|
+
type: 'conversation',
|
|
887
|
+
conversationId: detail.ConversationID,
|
|
888
|
+
messageId: conversationDetailId,
|
|
889
|
+
artifactId: artifactInfo.artifactId,
|
|
890
|
+
versionNumber: artifactInfo.versionNumber
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
notification.Unread = true; // Default unread
|
|
894
|
+
// ResourceTypeID and ResourceRecordID left null - using custom navigation
|
|
895
|
+
|
|
896
|
+
if (!(await notification.Save())) {
|
|
897
|
+
throw new Error('Failed to save notification');
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
LogStatus(`📬 Created notification ${notification.ID} for user ${contextUser.ID}`);
|
|
901
|
+
|
|
902
|
+
// Publish real-time notification event so client updates immediately
|
|
903
|
+
pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
904
|
+
userPayload: JSON.stringify(userPayload),
|
|
905
|
+
message: JSON.stringify({
|
|
906
|
+
type: 'notification',
|
|
907
|
+
notificationId: notification.ID,
|
|
908
|
+
action: 'create',
|
|
909
|
+
title: notification.Title,
|
|
910
|
+
message: notification.Message
|
|
911
|
+
})
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
LogStatus(`📡 Published notification event to client`);
|
|
915
|
+
|
|
916
|
+
} catch (error) {
|
|
917
|
+
LogError(`Failed to create completion notification: ${(error as Error).message}`);
|
|
918
|
+
// Don't throw - notification failure shouldn't fail the agent run
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
534
922
|
}
|
|
@@ -53,7 +53,8 @@ export class TaskOrchestrationResolver extends ResolverBase {
|
|
|
53
53
|
@Arg('environmentId') environmentId: string,
|
|
54
54
|
@Arg('sessionId') sessionId: string,
|
|
55
55
|
@PubSub() pubSub: PubSubEngine,
|
|
56
|
-
@Ctx() { userPayload }: AppContext
|
|
56
|
+
@Ctx() { userPayload }: AppContext,
|
|
57
|
+
@Arg('createNotifications', { nullable: true }) createNotifications?: boolean
|
|
57
58
|
): Promise<ExecuteTaskGraphResult> {
|
|
58
59
|
try {
|
|
59
60
|
LogStatus(`=== EXECUTING TASK GRAPH FOR CONVERSATION: ${conversationDetailId} ===`);
|
|
@@ -78,7 +79,7 @@ export class TaskOrchestrationResolver extends ResolverBase {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
// Create task orchestrator with PubSub for progress updates
|
|
81
|
-
const orchestrator = new TaskOrchestrator(currentUser, pubSub, sessionId, userPayload);
|
|
82
|
+
const orchestrator = new TaskOrchestrator(currentUser, pubSub, sessionId, userPayload, createNotifications || false);
|
|
82
83
|
|
|
83
84
|
// Create parent task and child tasks with dependencies
|
|
84
85
|
const { parentTaskId, taskIdMap } = await orchestrator.createTasksFromGraph(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Metadata, RunView, UserInfo, LogError, LogStatus } from '@memberjunction/core';
|
|
2
|
-
import { TaskEntity, TaskDependencyEntity, TaskTypeEntity, AIAgentEntityExtended, ConversationDetailEntity, ArtifactEntity, ArtifactVersionEntity, ConversationDetailArtifactEntity } from '@memberjunction/core-entities';
|
|
2
|
+
import { TaskEntity, TaskDependencyEntity, TaskTypeEntity, AIAgentEntityExtended, ConversationDetailEntity, ArtifactEntity, ArtifactVersionEntity, ConversationDetailArtifactEntity, UserNotificationEntity } from '@memberjunction/core-entities';
|
|
3
3
|
import { AgentRunner } from '@memberjunction/ai-agents';
|
|
4
4
|
import { ChatMessageRole } from '@memberjunction/ai';
|
|
5
5
|
import { PubSubEngine } from 'type-graphql';
|
|
@@ -50,7 +50,8 @@ export class TaskOrchestrator {
|
|
|
50
50
|
private contextUser: UserInfo,
|
|
51
51
|
private pubSub?: PubSubEngine,
|
|
52
52
|
private sessionId?: string,
|
|
53
|
-
private userPayload?: UserPayload
|
|
53
|
+
private userPayload?: UserPayload,
|
|
54
|
+
private createNotifications: boolean = false
|
|
54
55
|
) {}
|
|
55
56
|
|
|
56
57
|
/**
|
|
@@ -408,9 +409,14 @@ export class TaskOrchestrator {
|
|
|
408
409
|
parentTask.Status = 'Complete';
|
|
409
410
|
parentTask.PercentComplete = 100;
|
|
410
411
|
parentTask.CompletedAt = new Date();
|
|
411
|
-
await parentTask.Save();
|
|
412
|
+
const saved = await parentTask.Save();
|
|
412
413
|
|
|
413
414
|
LogStatus(`Parent workflow task completed: ${parentTask.Name}`);
|
|
415
|
+
|
|
416
|
+
// If notifications enabled, create user notification
|
|
417
|
+
if (this.createNotifications && saved) {
|
|
418
|
+
await this.createTaskGraphCompletionNotification(parentTask);
|
|
419
|
+
}
|
|
414
420
|
}
|
|
415
421
|
|
|
416
422
|
/**
|
|
@@ -703,6 +709,16 @@ export class TaskOrchestrator {
|
|
|
703
709
|
artifact.UserID = this.contextUser.ID;
|
|
704
710
|
artifact.EnvironmentID = (this.contextUser as any).EnvironmentID || 'F51358F3-9447-4176-B313-BF8025FD8D09';
|
|
705
711
|
|
|
712
|
+
// Set visibility based on agent's ArtifactCreationMode
|
|
713
|
+
// Will compile after CodeGen adds the new fields
|
|
714
|
+
const creationMode = agent.ArtifactCreationMode;
|
|
715
|
+
if (creationMode === 'System Only') {
|
|
716
|
+
artifact.Visibility = 'System Only';
|
|
717
|
+
LogStatus(`Task artifact marked as "System Only" per agent configuration`);
|
|
718
|
+
} else {
|
|
719
|
+
artifact.Visibility = 'Always';
|
|
720
|
+
}
|
|
721
|
+
|
|
706
722
|
const artifactSaved = await artifact.Save();
|
|
707
723
|
if (!artifactSaved) {
|
|
708
724
|
LogError('Failed to save artifact');
|
|
@@ -733,6 +749,23 @@ export class TaskOrchestrator {
|
|
|
733
749
|
|
|
734
750
|
LogStatus(`Created artifact version: ${version.ID}`);
|
|
735
751
|
|
|
752
|
+
// Check for extracted Name attribute and update artifact with better name
|
|
753
|
+
const nameAttr = (version as any).Attributes?.find((attr: any) =>
|
|
754
|
+
attr.StandardProperty === 'name' || attr.Name?.toLowerCase() === 'name'
|
|
755
|
+
);
|
|
756
|
+
|
|
757
|
+
// Check for valid name value (not null, not empty, not string "null")
|
|
758
|
+
let extractedName = nameAttr?.Value?.trim();
|
|
759
|
+
if (extractedName && extractedName.toLowerCase() !== 'null') {
|
|
760
|
+
// Strip surrounding quotes (double or single) from start and end
|
|
761
|
+
extractedName = extractedName.replace(/^["']|["']$/g, '');
|
|
762
|
+
|
|
763
|
+
artifact.Name = extractedName;
|
|
764
|
+
if (await artifact.Save()) {
|
|
765
|
+
LogStatus(`✨ Updated artifact name to: ${artifact.Name}`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
736
769
|
// Create M2M relationship linking artifact to conversation detail
|
|
737
770
|
const junction = await md.GetEntityObject<ConversationDetailArtifactEntity>(
|
|
738
771
|
'MJ: Conversation Detail Artifacts',
|
|
@@ -753,4 +786,86 @@ export class TaskOrchestrator {
|
|
|
753
786
|
LogError(`Error creating artifact from output: ${error}`);
|
|
754
787
|
}
|
|
755
788
|
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Create user notification for task graph completion
|
|
792
|
+
* Notifies user that their multi-step workflow has completed
|
|
793
|
+
*/
|
|
794
|
+
private async createTaskGraphCompletionNotification(parentTask: TaskEntity): Promise<void> {
|
|
795
|
+
try {
|
|
796
|
+
if (!parentTask.ConversationDetailID) {
|
|
797
|
+
LogStatus('Skipping notification - no conversation detail linked');
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const md = new Metadata();
|
|
802
|
+
|
|
803
|
+
// Load conversation detail to get conversation ID
|
|
804
|
+
const detail = await md.GetEntityObject<ConversationDetailEntity>(
|
|
805
|
+
'Conversation Details',
|
|
806
|
+
this.contextUser
|
|
807
|
+
);
|
|
808
|
+
if (!(await detail.Load(parentTask.ConversationDetailID))) {
|
|
809
|
+
throw new Error(`Failed to load conversation detail ${parentTask.ConversationDetailID}`);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Count child tasks and success rate
|
|
813
|
+
const rv = new RunView();
|
|
814
|
+
const tasksResult = await rv.RunView<TaskEntity>({
|
|
815
|
+
EntityName: 'MJ: Tasks',
|
|
816
|
+
ExtraFilter: `ParentID='${parentTask.ID}'`,
|
|
817
|
+
ResultType: 'entity_object'
|
|
818
|
+
}, this.contextUser);
|
|
819
|
+
|
|
820
|
+
const childTasks = tasksResult.Success ? (tasksResult.Results || []) : [];
|
|
821
|
+
const successCount = childTasks.filter(t => t.Status === 'Complete').length;
|
|
822
|
+
const totalCount = childTasks.length;
|
|
823
|
+
|
|
824
|
+
// Create notification
|
|
825
|
+
const notification = await md.GetEntityObject<UserNotificationEntity>(
|
|
826
|
+
'User Notifications',
|
|
827
|
+
this.contextUser
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
notification.UserID = this.contextUser.ID;
|
|
831
|
+
notification.Title = `Workflow "${parentTask.Name}" completed`;
|
|
832
|
+
notification.Message = `Your ${totalCount}-step workflow has finished. ${successCount} of ${totalCount} tasks completed successfully.`;
|
|
833
|
+
|
|
834
|
+
// Navigation configuration
|
|
835
|
+
notification.ResourceConfiguration = JSON.stringify({
|
|
836
|
+
type: 'conversation',
|
|
837
|
+
conversationId: detail.ConversationID,
|
|
838
|
+
messageId: parentTask.ConversationDetailID,
|
|
839
|
+
taskId: parentTask.ID
|
|
840
|
+
});
|
|
841
|
+
|
|
842
|
+
notification.Unread = true;
|
|
843
|
+
|
|
844
|
+
if (!(await notification.Save())) {
|
|
845
|
+
throw new Error('Failed to save notification');
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
LogStatus(`📬 Created task graph notification ${notification.ID} for user ${this.contextUser.ID}`);
|
|
849
|
+
|
|
850
|
+
// Publish real-time event if pubSub available
|
|
851
|
+
if (this.pubSub && this.userPayload) {
|
|
852
|
+
this.pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
853
|
+
userPayload: JSON.stringify(this.userPayload),
|
|
854
|
+
message: JSON.stringify({
|
|
855
|
+
type: 'notification',
|
|
856
|
+
notificationId: notification.ID,
|
|
857
|
+
action: 'create',
|
|
858
|
+
title: notification.Title,
|
|
859
|
+
message: notification.Message
|
|
860
|
+
})
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
LogStatus(`📡 Published task graph notification event to client`);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
} catch (error) {
|
|
867
|
+
LogError(`Failed to create task graph notification: ${(error as Error).message}`);
|
|
868
|
+
// Don't throw - notification failure shouldn't fail the task
|
|
869
|
+
}
|
|
870
|
+
}
|
|
756
871
|
}
|