@memberjunction/server 2.111.0 → 2.112.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/skip-agent.d.ts +4 -4
- package/dist/agents/skip-agent.d.ts.map +1 -1
- package/dist/agents/skip-agent.js +808 -951
- package/dist/agents/skip-agent.js.map +1 -1
- package/dist/agents/skip-sdk.d.ts +1 -1
- package/dist/agents/skip-sdk.d.ts.map +1 -1
- package/dist/agents/skip-sdk.js +53 -43
- package/dist/agents/skip-sdk.js.map +1 -1
- package/dist/apolloServer/index.js +1 -1
- package/dist/auth/AuthProviderFactory.d.ts +1 -1
- package/dist/auth/AuthProviderFactory.d.ts.map +1 -1
- package/dist/auth/AuthProviderFactory.js +1 -3
- package/dist/auth/AuthProviderFactory.js.map +1 -1
- package/dist/auth/BaseAuthProvider.d.ts +1 -1
- package/dist/auth/BaseAuthProvider.d.ts.map +1 -1
- package/dist/auth/BaseAuthProvider.js +3 -2
- package/dist/auth/BaseAuthProvider.js.map +1 -1
- package/dist/auth/IAuthProvider.d.ts +1 -1
- package/dist/auth/IAuthProvider.d.ts.map +1 -1
- package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
- package/dist/auth/exampleNewUserSubClass.js +1 -1
- package/dist/auth/exampleNewUserSubClass.js.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +6 -6
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/initializeProviders.js +1 -1
- package/dist/auth/initializeProviders.js.map +1 -1
- package/dist/auth/newUsers.d.ts +1 -1
- package/dist/auth/newUsers.d.ts.map +1 -1
- package/dist/auth/newUsers.js +7 -7
- package/dist/auth/newUsers.js.map +1 -1
- package/dist/auth/providers/Auth0Provider.d.ts +1 -1
- package/dist/auth/providers/Auth0Provider.d.ts.map +1 -1
- package/dist/auth/providers/Auth0Provider.js +1 -1
- package/dist/auth/providers/Auth0Provider.js.map +1 -1
- package/dist/auth/providers/CognitoProvider.d.ts +1 -1
- package/dist/auth/providers/CognitoProvider.d.ts.map +1 -1
- package/dist/auth/providers/CognitoProvider.js +3 -6
- package/dist/auth/providers/CognitoProvider.js.map +1 -1
- package/dist/auth/providers/GoogleProvider.d.ts +1 -1
- package/dist/auth/providers/GoogleProvider.d.ts.map +1 -1
- package/dist/auth/providers/GoogleProvider.js +1 -1
- package/dist/auth/providers/GoogleProvider.js.map +1 -1
- package/dist/auth/providers/MSALProvider.d.ts +1 -1
- package/dist/auth/providers/MSALProvider.d.ts.map +1 -1
- package/dist/auth/providers/MSALProvider.js +1 -1
- package/dist/auth/providers/MSALProvider.js.map +1 -1
- package/dist/auth/providers/OktaProvider.d.ts +1 -1
- package/dist/auth/providers/OktaProvider.d.ts.map +1 -1
- package/dist/auth/providers/OktaProvider.js +1 -1
- package/dist/auth/providers/OktaProvider.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +22 -10
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +9 -7
- package/dist/context.js.map +1 -1
- package/dist/entitySubclasses/entityPermissions.server.d.ts +1 -1
- package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -1
- package/dist/entitySubclasses/entityPermissions.server.js +1 -1
- package/dist/entitySubclasses/entityPermissions.server.js.map +1 -1
- package/dist/generated/generated.d.ts +648 -648
- package/dist/generated/generated.d.ts.map +1 -1
- package/dist/generated/generated.js +2986 -1133
- package/dist/generated/generated.js.map +1 -1
- package/dist/generic/KeyInputOutputTypes.d.ts +1 -1
- package/dist/generic/KeyInputOutputTypes.d.ts.map +1 -1
- package/dist/generic/KeyInputOutputTypes.js +1 -1
- package/dist/generic/KeyInputOutputTypes.js.map +1 -1
- package/dist/generic/ResolverBase.d.ts +1 -1
- package/dist/generic/ResolverBase.d.ts.map +1 -1
- package/dist/generic/ResolverBase.js +15 -10
- package/dist/generic/ResolverBase.js.map +1 -1
- package/dist/generic/RunViewResolver.d.ts +1 -1
- package/dist/generic/RunViewResolver.d.ts.map +1 -1
- package/dist/generic/RunViewResolver.js +15 -15
- package/dist/generic/RunViewResolver.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -9
- package/dist/index.js.map +1 -1
- package/dist/resolvers/ActionResolver.d.ts +2 -2
- package/dist/resolvers/ActionResolver.d.ts.map +1 -1
- package/dist/resolvers/ActionResolver.js +28 -30
- package/dist/resolvers/ActionResolver.js.map +1 -1
- package/dist/resolvers/AskSkipResolver.d.ts +2 -2
- package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
- package/dist/resolvers/AskSkipResolver.js +60 -50
- package/dist/resolvers/AskSkipResolver.js.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
- package/dist/resolvers/ComponentRegistryResolver.js +36 -38
- package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
- package/dist/resolvers/CreateQueryResolver.d.ts +1 -1
- package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
- package/dist/resolvers/CreateQueryResolver.js +43 -40
- package/dist/resolvers/CreateQueryResolver.js.map +1 -1
- package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
- package/dist/resolvers/DatasetResolver.js +1 -1
- package/dist/resolvers/DatasetResolver.js.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts +1 -1
- package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js +1 -1
- package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
- package/dist/resolvers/EntityResolver.d.ts.map +1 -1
- package/dist/resolvers/EntityResolver.js +1 -1
- package/dist/resolvers/EntityResolver.js.map +1 -1
- package/dist/resolvers/FileCategoryResolver.js +1 -1
- package/dist/resolvers/FileCategoryResolver.js.map +1 -1
- package/dist/resolvers/FileResolver.js +1 -1
- package/dist/resolvers/FileResolver.js.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts +1 -1
- package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataContextDataResolver.js +5 -5
- package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
- package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
- package/dist/resolvers/GetDataResolver.js +8 -6
- package/dist/resolvers/GetDataResolver.js.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.d.ts +3 -3
- package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
- package/dist/resolvers/MergeRecordsResolver.js +3 -3
- package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js +1 -1
- package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
- package/dist/resolvers/QueryResolver.d.ts.map +1 -1
- package/dist/resolvers/QueryResolver.js +11 -11
- package/dist/resolvers/QueryResolver.js.map +1 -1
- package/dist/resolvers/ReportResolver.js +1 -1
- package/dist/resolvers/ReportResolver.js.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIAgentResolver.js +27 -28
- package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
- package/dist/resolvers/RunAIPromptResolver.js +31 -31
- package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
- package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
- package/dist/resolvers/RunTemplateResolver.js +9 -9
- package/dist/resolvers/RunTemplateResolver.js.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
- package/dist/resolvers/SqlLoggingConfigResolver.js +10 -10
- package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts +1 -1
- package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncDataResolver.js +15 -14
- package/dist/resolvers/SyncDataResolver.js.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
- package/dist/resolvers/SyncRolesUsersResolver.js +48 -44
- package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
- package/dist/resolvers/TaskResolver.d.ts.map +1 -1
- package/dist/resolvers/TaskResolver.js +7 -7
- package/dist/resolvers/TaskResolver.js.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts +1 -1
- package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
- package/dist/resolvers/TransactionGroupResolver.js +12 -12
- package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.d.ts +1 -1
- package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
- package/dist/resolvers/UserFavoriteResolver.js +1 -1
- package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
- package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
- package/dist/resolvers/UserViewResolver.js.map +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts +1 -1
- package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
- package/dist/rest/EntityCRUDHandler.js +14 -16
- package/dist/rest/EntityCRUDHandler.js.map +1 -1
- package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
- package/dist/rest/RESTEndpointHandler.js +23 -25
- package/dist/rest/RESTEndpointHandler.js.map +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts +1 -1
- package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
- package/dist/rest/ViewOperationsHandler.js +17 -21
- package/dist/rest/ViewOperationsHandler.js.map +1 -1
- package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -1
- package/dist/scheduler/LearningCycleScheduler.js.map +1 -1
- package/dist/services/ScheduledJobsService.d.ts.map +1 -1
- package/dist/services/ScheduledJobsService.js +4 -6
- package/dist/services/ScheduledJobsService.js.map +1 -1
- package/dist/services/TaskOrchestrator.d.ts +1 -1
- package/dist/services/TaskOrchestrator.d.ts.map +1 -1
- package/dist/services/TaskOrchestrator.js +30 -30
- package/dist/services/TaskOrchestrator.js.map +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -1
- package/dist/types.js.map +1 -1
- package/dist/util.d.ts +1 -1
- package/dist/util.d.ts.map +1 -1
- package/dist/util.js +2 -2
- package/dist/util.js.map +1 -1
- package/package.json +36 -37
- package/src/agents/skip-agent.ts +1067 -1200
- package/src/agents/skip-sdk.ts +877 -851
- package/src/apolloServer/index.ts +2 -2
- package/src/auth/AuthProviderFactory.ts +8 -14
- package/src/auth/BaseAuthProvider.ts +5 -4
- package/src/auth/IAuthProvider.ts +2 -2
- package/src/auth/exampleNewUserSubClass.ts +9 -2
- package/src/auth/index.ts +31 -26
- package/src/auth/initializeProviders.ts +3 -3
- package/src/auth/newUsers.ts +166 -134
- package/src/auth/providers/Auth0Provider.ts +5 -5
- package/src/auth/providers/CognitoProvider.ts +7 -10
- package/src/auth/providers/GoogleProvider.ts +4 -5
- package/src/auth/providers/MSALProvider.ts +5 -5
- package/src/auth/providers/OktaProvider.ts +6 -7
- package/src/config.ts +63 -54
- package/src/context.ts +42 -30
- package/src/entitySubclasses/entityPermissions.server.ts +3 -3
- package/src/generated/generated.ts +48130 -39930
- package/src/generic/KeyInputOutputTypes.ts +3 -6
- package/src/generic/ResolverBase.ts +119 -78
- package/src/generic/RunViewResolver.ts +27 -23
- package/src/index.ts +66 -42
- package/src/resolvers/ActionResolver.ts +46 -57
- package/src/resolvers/AskSkipResolver.ts +607 -533
- package/src/resolvers/ComponentRegistryResolver.ts +547 -562
- package/src/resolvers/CreateQueryResolver.ts +683 -655
- package/src/resolvers/DatasetResolver.ts +5 -6
- package/src/resolvers/EntityCommunicationsResolver.ts +1 -1
- package/src/resolvers/EntityRecordNameResolver.ts +9 -5
- package/src/resolvers/EntityResolver.ts +9 -7
- package/src/resolvers/FileCategoryResolver.ts +2 -2
- package/src/resolvers/FileResolver.ts +4 -4
- package/src/resolvers/GetDataContextDataResolver.ts +106 -118
- package/src/resolvers/GetDataResolver.ts +194 -205
- package/src/resolvers/MergeRecordsResolver.ts +5 -5
- package/src/resolvers/PotentialDuplicateRecordResolver.ts +1 -1
- package/src/resolvers/QueryResolver.ts +95 -78
- package/src/resolvers/ReportResolver.ts +2 -2
- package/src/resolvers/RunAIAgentResolver.ts +818 -828
- package/src/resolvers/RunAIPromptResolver.ts +693 -709
- package/src/resolvers/RunTemplateResolver.ts +105 -103
- package/src/resolvers/SqlLoggingConfigResolver.ts +69 -72
- package/src/resolvers/SyncDataResolver.ts +386 -352
- package/src/resolvers/SyncRolesUsersResolver.ts +387 -350
- package/src/resolvers/TaskResolver.ts +110 -115
- package/src/resolvers/TransactionGroupResolver.ts +143 -138
- package/src/resolvers/UserFavoriteResolver.ts +17 -8
- package/src/resolvers/UserViewResolver.ts +17 -12
- package/src/rest/EntityCRUDHandler.ts +291 -268
- package/src/rest/RESTEndpointHandler.ts +782 -776
- package/src/rest/ViewOperationsHandler.ts +191 -195
- package/src/scheduler/LearningCycleScheduler.ts +8 -52
- package/src/services/ScheduledJobsService.ts +129 -132
- package/src/services/TaskOrchestrator.ts +792 -776
- package/src/types.ts +15 -9
- package/src/util.ts +112 -109
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import { Metadata, RunView, UserInfo, LogError, LogStatus } from '@memberjunction/
|
|
2
|
-
import {
|
|
1
|
+
import { Metadata, RunView, UserInfo, LogError, LogStatus } from '@memberjunction/global';
|
|
2
|
+
import {
|
|
3
|
+
TaskEntity,
|
|
4
|
+
TaskDependencyEntity,
|
|
5
|
+
TaskTypeEntity,
|
|
6
|
+
AIAgentEntityExtended,
|
|
7
|
+
ConversationDetailEntity,
|
|
8
|
+
ArtifactEntity,
|
|
9
|
+
ArtifactVersionEntity,
|
|
10
|
+
ConversationDetailArtifactEntity,
|
|
11
|
+
UserNotificationEntity,
|
|
12
|
+
} from '@memberjunction/core-entities';
|
|
3
13
|
import { AgentRunner } from '@memberjunction/ai-agents';
|
|
4
14
|
import { ChatMessageRole } from '@memberjunction/ai';
|
|
5
15
|
import { PubSubEngine } from 'type-graphql';
|
|
@@ -10,862 +20,868 @@ import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
|
|
|
10
20
|
* Task definition from LLM response
|
|
11
21
|
*/
|
|
12
22
|
export interface TaskDefinition {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
tempId: string; // LLM-generated ID for reference
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
agentName: string;
|
|
27
|
+
dependsOn: string[]; // Array of tempIds this task depends on
|
|
28
|
+
inputPayload?: any;
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
/**
|
|
22
32
|
* Task graph response from Conversation Manager
|
|
23
33
|
*/
|
|
24
34
|
export interface TaskGraphResponse {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
workflowName: string; // Name for the parent/workflow task
|
|
36
|
+
tasks: TaskDefinition[];
|
|
37
|
+
reasoning?: string;
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
/**
|
|
31
41
|
* Task execution result
|
|
32
42
|
*/
|
|
33
43
|
export interface TaskExecutionResult {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
44
|
+
taskId: string;
|
|
45
|
+
success: boolean;
|
|
46
|
+
output?: any;
|
|
47
|
+
error?: string;
|
|
38
48
|
}
|
|
39
49
|
|
|
40
50
|
/**
|
|
41
51
|
* TaskOrchestrator handles multi-step task execution with dependencies
|
|
42
52
|
*/
|
|
43
53
|
export class TaskOrchestrator {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const rv = new RunView();
|
|
66
|
-
const result = await rv.RunView({
|
|
67
|
-
EntityName: 'MJ: Task Types',
|
|
68
|
-
ExtraFilter: `Name='AI Agent Execution'`,
|
|
69
|
-
ResultType: 'entity_object'
|
|
70
|
-
}, this.contextUser);
|
|
71
|
-
|
|
72
|
-
if (result.Success && result.Results && result.Results.length > 0) {
|
|
73
|
-
this.taskTypeId = result.Results[0].ID;
|
|
74
|
-
return this.taskTypeId;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Create the task type if it doesn't exist
|
|
78
|
-
const md = new Metadata();
|
|
79
|
-
const taskType = await md.GetEntityObject<TaskTypeEntity>('MJ: Task Types', this.contextUser);
|
|
80
|
-
taskType.Name = 'AI Agent Execution';
|
|
81
|
-
taskType.Description = 'Task executed by an AI agent as part of conversation workflow';
|
|
82
|
-
|
|
83
|
-
const saved = await taskType.Save();
|
|
84
|
-
if (!saved) {
|
|
85
|
-
throw new Error('Failed to create AI Agent Execution task type');
|
|
86
|
-
}
|
|
54
|
+
// Default artifact type ID for JSON (when agent doesn't specify DefaultArtifactTypeID)
|
|
55
|
+
private readonly JSON_ARTIFACT_TYPE_ID = 'ae674c7e-ea0d-49ea-89e4-0649f5eb20d4';
|
|
56
|
+
|
|
57
|
+
private taskTypeId: string | null = null;
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
private contextUser: UserInfo,
|
|
61
|
+
private pubSub?: PubSubEngine,
|
|
62
|
+
private sessionId?: string,
|
|
63
|
+
private userPayload?: UserPayload,
|
|
64
|
+
private createNotifications: boolean = false
|
|
65
|
+
) {}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Initialize the orchestrator by finding/creating the AI Agent Task type
|
|
69
|
+
*/
|
|
70
|
+
private async ensureTaskType(): Promise<string> {
|
|
71
|
+
if (this.taskTypeId) {
|
|
72
|
+
return this.taskTypeId;
|
|
73
|
+
}
|
|
87
74
|
|
|
88
|
-
|
|
89
|
-
|
|
75
|
+
const rv = new RunView();
|
|
76
|
+
const result = await rv.RunView(
|
|
77
|
+
{
|
|
78
|
+
EntityName: 'MJ: Task Types',
|
|
79
|
+
ExtraFilter: `Name='AI Agent Execution'`,
|
|
80
|
+
ResultType: 'entity_object',
|
|
81
|
+
},
|
|
82
|
+
this.contextUser
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
if (result.Success && result.Results && result.Results.length > 0) {
|
|
86
|
+
this.taskTypeId = result.Results[0].ID;
|
|
87
|
+
return this.taskTypeId;
|
|
90
88
|
}
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
* @returns Object with parentTaskId and map of tempId -> actual TaskEntity ID
|
|
98
|
-
*/
|
|
99
|
-
async createTasksFromGraph(
|
|
100
|
-
taskGraph: TaskGraphResponse,
|
|
101
|
-
conversationDetailId: string,
|
|
102
|
-
environmentId: string
|
|
103
|
-
): Promise<{ parentTaskId: string; taskIdMap: Map<string, string> }> {
|
|
104
|
-
const taskTypeId = await this.ensureTaskType();
|
|
105
|
-
const md = new Metadata();
|
|
106
|
-
const tempIdToRealId = new Map<string, string>();
|
|
107
|
-
|
|
108
|
-
// Create parent workflow task
|
|
109
|
-
const parentTask = await md.GetEntityObject<TaskEntity>('MJ: Tasks', this.contextUser);
|
|
110
|
-
parentTask.Name = taskGraph.workflowName;
|
|
111
|
-
parentTask.Description = taskGraph.reasoning || 'AI-orchestrated workflow';
|
|
112
|
-
parentTask.TypeID = taskTypeId;
|
|
113
|
-
parentTask.EnvironmentID = environmentId;
|
|
114
|
-
parentTask.ConversationDetailID = conversationDetailId; // Parent links to conversation
|
|
115
|
-
parentTask.Status = 'In Progress'; // Workflow is in progress
|
|
116
|
-
parentTask.PercentComplete = 0;
|
|
117
|
-
|
|
118
|
-
const parentSaved = await parentTask.Save();
|
|
119
|
-
if (!parentSaved) {
|
|
120
|
-
throw new Error('Failed to create parent workflow task');
|
|
121
|
-
}
|
|
90
|
+
// Create the task type if it doesn't exist
|
|
91
|
+
const md = new Metadata();
|
|
92
|
+
const taskType = await md.GetEntityObject<TaskTypeEntity>('MJ: Task Types', this.contextUser);
|
|
93
|
+
taskType.Name = 'AI Agent Execution';
|
|
94
|
+
taskType.Description = 'Task executed by an AI agent as part of conversation workflow';
|
|
122
95
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const uniqueTasks = taskGraph.tasks.filter(task => {
|
|
128
|
-
if (seenTempIds.has(task.tempId)) {
|
|
129
|
-
LogError(`Duplicate tempId detected and ignored: ${task.tempId} (${task.name})`);
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
seenTempIds.add(task.tempId);
|
|
133
|
-
return true;
|
|
134
|
-
});
|
|
96
|
+
const saved = await taskType.Save();
|
|
97
|
+
if (!saved) {
|
|
98
|
+
throw new Error('Failed to create AI Agent Execution task type');
|
|
99
|
+
}
|
|
135
100
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (saved) {
|
|
171
|
-
tempIdToRealId.set(taskDef.tempId, task.ID);
|
|
172
|
-
LogStatus(`Created child task: ${task.Name} (${task.ID}) under parent ${parentTask.ID}`);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
101
|
+
this.taskTypeId = taskType.ID;
|
|
102
|
+
return this.taskTypeId;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create tasks from LLM task graph response
|
|
107
|
+
* @param taskGraph Task graph from Conversation Manager
|
|
108
|
+
* @param conversationDetailId ID of the conversation detail that triggered this
|
|
109
|
+
* @param environmentId Environment ID
|
|
110
|
+
* @returns Object with parentTaskId and map of tempId -> actual TaskEntity ID
|
|
111
|
+
*/
|
|
112
|
+
async createTasksFromGraph(
|
|
113
|
+
taskGraph: TaskGraphResponse,
|
|
114
|
+
conversationDetailId: string,
|
|
115
|
+
environmentId: string
|
|
116
|
+
): Promise<{ parentTaskId: string; taskIdMap: Map<string, string> }> {
|
|
117
|
+
const taskTypeId = await this.ensureTaskType();
|
|
118
|
+
const md = new Metadata();
|
|
119
|
+
const tempIdToRealId = new Map<string, string>();
|
|
120
|
+
|
|
121
|
+
// Create parent workflow task
|
|
122
|
+
const parentTask = await md.GetEntityObject<TaskEntity>('MJ: Tasks', this.contextUser);
|
|
123
|
+
parentTask.Name = taskGraph.workflowName;
|
|
124
|
+
parentTask.Description = taskGraph.reasoning || 'AI-orchestrated workflow';
|
|
125
|
+
parentTask.TypeID = taskTypeId;
|
|
126
|
+
parentTask.EnvironmentID = environmentId;
|
|
127
|
+
parentTask.ConversationDetailID = conversationDetailId; // Parent links to conversation
|
|
128
|
+
parentTask.Status = 'In Progress'; // Workflow is in progress
|
|
129
|
+
parentTask.PercentComplete = 0;
|
|
130
|
+
|
|
131
|
+
const parentSaved = await parentTask.Save();
|
|
132
|
+
if (!parentSaved) {
|
|
133
|
+
throw new Error('Failed to create parent workflow task');
|
|
134
|
+
}
|
|
175
135
|
|
|
176
|
-
|
|
177
|
-
for (const taskDef of uniqueTasks) {
|
|
178
|
-
const taskId = tempIdToRealId.get(taskDef.tempId);
|
|
179
|
-
if (!taskId) continue;
|
|
180
|
-
|
|
181
|
-
for (const dependsOnTempId of taskDef.dependsOn) {
|
|
182
|
-
const dependsOnId = tempIdToRealId.get(dependsOnTempId);
|
|
183
|
-
if (!dependsOnId) {
|
|
184
|
-
LogError(`Dependency not found: ${dependsOnTempId}`);
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const dependency = await md.GetEntityObject<TaskDependencyEntity>('MJ: Task Dependencies', this.contextUser);
|
|
189
|
-
dependency.TaskID = taskId;
|
|
190
|
-
dependency.DependsOnTaskID = dependsOnId;
|
|
191
|
-
dependency.DependencyType = 'Prerequisite';
|
|
192
|
-
|
|
193
|
-
await dependency.Save();
|
|
194
|
-
LogStatus(`Created dependency: Task ${taskId} depends on ${dependsOnId}`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
136
|
+
LogStatus(`Created parent workflow task: ${parentTask.Name} (${parentTask.ID})`);
|
|
197
137
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
138
|
+
// Deduplicate tasks by tempId (LLM sometimes returns duplicates)
|
|
139
|
+
const seenTempIds = new Set<string>();
|
|
140
|
+
const uniqueTasks = taskGraph.tasks.filter((task) => {
|
|
141
|
+
if (seenTempIds.has(task.tempId)) {
|
|
142
|
+
LogError(`Duplicate tempId detected and ignored: ${task.tempId} (${task.name})`);
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
seenTempIds.add(task.tempId);
|
|
146
|
+
return true;
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
LogStatus(`Creating ${uniqueTasks.length} unique child tasks (${taskGraph.tasks.length - uniqueTasks.length} duplicates filtered)`);
|
|
150
|
+
|
|
151
|
+
// Create all child tasks
|
|
152
|
+
for (const taskDef of uniqueTasks) {
|
|
153
|
+
const task = await md.GetEntityObject<TaskEntity>('MJ: Tasks', this.contextUser);
|
|
154
|
+
|
|
155
|
+
// Find agent by name
|
|
156
|
+
const agent = await this.findAgentByName(taskDef.agentName);
|
|
157
|
+
if (!agent) {
|
|
158
|
+
LogError(`Agent not found: ${taskDef.agentName}`);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
task.Name = taskDef.name;
|
|
163
|
+
task.Description = taskDef.description;
|
|
164
|
+
task.TypeID = taskTypeId;
|
|
165
|
+
task.EnvironmentID = environmentId;
|
|
166
|
+
task.ParentID = parentTask.ID; // Link to parent task
|
|
167
|
+
task.ConversationDetailID = conversationDetailId; // Link to conversation so agent runs can be tracked
|
|
168
|
+
task.AgentID = agent.ID;
|
|
169
|
+
task.Status = 'Pending';
|
|
170
|
+
task.PercentComplete = 0;
|
|
171
|
+
|
|
172
|
+
// Store input payload if provided
|
|
173
|
+
if (taskDef.inputPayload) {
|
|
174
|
+
const metadata = {
|
|
175
|
+
inputPayload: taskDef.inputPayload,
|
|
176
|
+
tempId: taskDef.tempId,
|
|
201
177
|
};
|
|
178
|
+
// Store in a well-known format at the end of description
|
|
179
|
+
task.Description = `${taskDef.description}\n\n__TASK_METADATA__\n${JSON.stringify(metadata)}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const saved = await task.Save();
|
|
183
|
+
if (saved) {
|
|
184
|
+
tempIdToRealId.set(taskDef.tempId, task.ID);
|
|
185
|
+
LogStatus(`Created child task: ${task.Name} (${task.ID}) under parent ${parentTask.ID}`);
|
|
186
|
+
}
|
|
202
187
|
}
|
|
203
188
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (!this.pubSub || !this.sessionId || !this.userPayload) {
|
|
209
|
-
LogStatus(`⚠️ PubSub not available for progress updates (pubSub: ${!!this.pubSub}, sessionId: ${!!this.sessionId}, userPayload: ${!!this.userPayload})`);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
189
|
+
// Create dependencies between child tasks
|
|
190
|
+
for (const taskDef of uniqueTasks) {
|
|
191
|
+
const taskId = tempIdToRealId.get(taskDef.tempId);
|
|
192
|
+
if (!taskId) continue;
|
|
212
193
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
taskName,
|
|
220
|
-
message,
|
|
221
|
-
percentComplete,
|
|
222
|
-
timestamp: new Date()
|
|
223
|
-
}
|
|
224
|
-
}),
|
|
225
|
-
sessionId: this.userPayload.sessionId
|
|
226
|
-
};
|
|
194
|
+
for (const dependsOnTempId of taskDef.dependsOn) {
|
|
195
|
+
const dependsOnId = tempIdToRealId.get(dependsOnTempId);
|
|
196
|
+
if (!dependsOnId) {
|
|
197
|
+
LogError(`Dependency not found: ${dependsOnTempId}`);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
227
200
|
|
|
228
|
-
|
|
229
|
-
|
|
201
|
+
const dependency = await md.GetEntityObject<TaskDependencyEntity>('MJ: Task Dependencies', this.contextUser);
|
|
202
|
+
dependency.TaskID = taskId;
|
|
203
|
+
dependency.DependsOnTaskID = dependsOnId;
|
|
204
|
+
dependency.DependencyType = 'Prerequisite';
|
|
230
205
|
|
|
231
|
-
|
|
206
|
+
await dependency.Save();
|
|
207
|
+
LogStatus(`Created dependency: Task ${taskId} depends on ${dependsOnId}`);
|
|
208
|
+
}
|
|
232
209
|
}
|
|
233
210
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
agentStep,
|
|
251
|
-
agentMessage,
|
|
252
|
-
timestamp: new Date()
|
|
253
|
-
}
|
|
254
|
-
}),
|
|
255
|
-
sessionId: this.userPayload.sessionId
|
|
256
|
-
};
|
|
211
|
+
return {
|
|
212
|
+
parentTaskId: parentTask.ID,
|
|
213
|
+
taskIdMap: tempIdToRealId,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Publish task progress update via PubSub
|
|
219
|
+
*/
|
|
220
|
+
private publishTaskProgress(taskName: string, message: string, percentComplete: number): void {
|
|
221
|
+
if (!this.pubSub || !this.sessionId || !this.userPayload) {
|
|
222
|
+
LogStatus(
|
|
223
|
+
`⚠️ PubSub not available for progress updates (pubSub: ${!!this.pubSub}, sessionId: ${!!this.sessionId}, userPayload: ${!!this.userPayload})`
|
|
224
|
+
);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
257
227
|
|
|
258
|
-
|
|
259
|
-
|
|
228
|
+
const payload = {
|
|
229
|
+
message: JSON.stringify({
|
|
230
|
+
resolver: 'TaskOrchestrator',
|
|
231
|
+
type: 'TaskProgress',
|
|
232
|
+
status: 'ok',
|
|
233
|
+
data: {
|
|
234
|
+
taskName,
|
|
235
|
+
message,
|
|
236
|
+
percentComplete,
|
|
237
|
+
timestamp: new Date(),
|
|
238
|
+
},
|
|
239
|
+
}),
|
|
240
|
+
sessionId: this.userPayload.sessionId,
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
LogStatus(`📡 Publishing task progress: ${taskName} - ${message} (${percentComplete}%) to session ${this.userPayload.sessionId}`);
|
|
244
|
+
this.pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, payload);
|
|
245
|
+
|
|
246
|
+
LogStatus(`[Task: ${taskName}] ${message} (${percentComplete}%)`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Publish agent progress update (nested within task)
|
|
251
|
+
*/
|
|
252
|
+
private publishAgentProgress(taskName: string, agentStep: string, agentMessage: string): void {
|
|
253
|
+
if (!this.pubSub || !this.sessionId || !this.userPayload) {
|
|
254
|
+
LogStatus(
|
|
255
|
+
`⚠️ PubSub not available for agent progress (pubSub: ${!!this.pubSub}, sessionId: ${!!this.sessionId}, userPayload: ${!!this.userPayload})`
|
|
256
|
+
);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
260
259
|
|
|
261
|
-
|
|
260
|
+
const payload = {
|
|
261
|
+
message: JSON.stringify({
|
|
262
|
+
resolver: 'TaskOrchestrator',
|
|
263
|
+
type: 'AgentProgress',
|
|
264
|
+
status: 'ok',
|
|
265
|
+
data: {
|
|
266
|
+
taskName,
|
|
267
|
+
agentStep,
|
|
268
|
+
agentMessage,
|
|
269
|
+
timestamp: new Date(),
|
|
270
|
+
},
|
|
271
|
+
}),
|
|
272
|
+
sessionId: this.userPayload.sessionId,
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
LogStatus(`📡 Publishing agent progress: ${taskName} → ${agentStep} to session ${this.userPayload.sessionId}`);
|
|
276
|
+
this.pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, payload);
|
|
277
|
+
|
|
278
|
+
LogStatus(`[Task: ${taskName}] → ${agentStep}: ${agentMessage}`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Find agent by name
|
|
283
|
+
*/
|
|
284
|
+
private async findAgentByName(agentName: string): Promise<AIAgentEntityExtended | null> {
|
|
285
|
+
const rv = new RunView();
|
|
286
|
+
const result = await rv.RunView<AIAgentEntityExtended>(
|
|
287
|
+
{
|
|
288
|
+
EntityName: 'AI Agents',
|
|
289
|
+
ExtraFilter: `Name='${agentName.replace(/'/g, "''")}'`,
|
|
290
|
+
ResultType: 'entity_object',
|
|
291
|
+
},
|
|
292
|
+
this.contextUser
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
if (result.Success && result.Results && result.Results.length > 0) {
|
|
296
|
+
return result.Results[0];
|
|
262
297
|
}
|
|
263
298
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Execute all pending tasks for a parent task, respecting dependencies
|
|
304
|
+
* @param parentTaskId Parent task ID
|
|
305
|
+
* @returns Array of execution results
|
|
306
|
+
*/
|
|
307
|
+
async executeTasksForParent(parentTaskId: string): Promise<TaskExecutionResult[]> {
|
|
308
|
+
const results: TaskExecutionResult[] = [];
|
|
309
|
+
let hasMore = true;
|
|
310
|
+
|
|
311
|
+
// Get parent task for progress updates
|
|
312
|
+
const md = new Metadata();
|
|
313
|
+
const parentTask = await md.GetEntityObject<TaskEntity>('MJ: Tasks', this.contextUser);
|
|
314
|
+
await parentTask.Load(parentTaskId);
|
|
315
|
+
|
|
316
|
+
// Publish workflow start
|
|
317
|
+
this.publishTaskProgress(parentTask.Name, 'Starting workflow execution', 0);
|
|
318
|
+
|
|
319
|
+
while (hasMore) {
|
|
320
|
+
// Find tasks that are pending and have no incomplete dependencies
|
|
321
|
+
const eligibleTasks = await this.findEligibleTasks(parentTaskId);
|
|
322
|
+
|
|
323
|
+
if (eligibleTasks.length === 0) {
|
|
324
|
+
hasMore = false;
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Execute eligible tasks (could be parallelized in the future)
|
|
329
|
+
for (const task of eligibleTasks) {
|
|
330
|
+
// Publish task start
|
|
331
|
+
this.publishTaskProgress(task.Name, 'Starting task', 0);
|
|
332
|
+
|
|
333
|
+
const result = await this.executeTask(task);
|
|
334
|
+
results.push(result);
|
|
335
|
+
|
|
336
|
+
// Publish task complete
|
|
337
|
+
if (result.success) {
|
|
338
|
+
this.publishTaskProgress(task.Name, 'Task completed successfully', 100);
|
|
339
|
+
} else {
|
|
340
|
+
this.publishTaskProgress(task.Name, `Task failed: ${result.error}`, 100);
|
|
277
341
|
}
|
|
278
342
|
|
|
279
|
-
|
|
343
|
+
// Update parent task progress after each child completes
|
|
344
|
+
await this.updateParentTaskProgress(parentTaskId);
|
|
345
|
+
}
|
|
280
346
|
}
|
|
281
347
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
// Publish task start
|
|
311
|
-
this.publishTaskProgress(task.Name, 'Starting task', 0);
|
|
312
|
-
|
|
313
|
-
const result = await this.executeTask(task);
|
|
314
|
-
results.push(result);
|
|
315
|
-
|
|
316
|
-
// Publish task complete
|
|
317
|
-
if (result.success) {
|
|
318
|
-
this.publishTaskProgress(task.Name, 'Task completed successfully', 100);
|
|
319
|
-
} else {
|
|
320
|
-
this.publishTaskProgress(task.Name, `Task failed: ${result.error}`, 100);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// Update parent task progress after each child completes
|
|
324
|
-
await this.updateParentTaskProgress(parentTaskId);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Mark parent task as complete
|
|
329
|
-
await this.completeParentTask(parentTaskId);
|
|
348
|
+
// Mark parent task as complete
|
|
349
|
+
await this.completeParentTask(parentTaskId);
|
|
350
|
+
|
|
351
|
+
// Publish workflow complete
|
|
352
|
+
this.publishTaskProgress(parentTask.Name, 'Workflow completed', 100);
|
|
353
|
+
|
|
354
|
+
return results;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Find tasks that are ready to execute (pending with no incomplete dependencies)
|
|
359
|
+
*/
|
|
360
|
+
private async findEligibleTasks(parentTaskId: string): Promise<TaskEntity[]> {
|
|
361
|
+
const rv = new RunView();
|
|
362
|
+
|
|
363
|
+
// Get all pending tasks for this parent
|
|
364
|
+
const tasksResult = await rv.RunView<TaskEntity>(
|
|
365
|
+
{
|
|
366
|
+
EntityName: 'MJ: Tasks',
|
|
367
|
+
ExtraFilter: `ParentID='${parentTaskId}' AND Status='Pending'`,
|
|
368
|
+
ResultType: 'entity_object',
|
|
369
|
+
},
|
|
370
|
+
this.contextUser
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
if (!tasksResult.Success || !tasksResult.Results) {
|
|
374
|
+
return [];
|
|
375
|
+
}
|
|
330
376
|
|
|
331
|
-
|
|
332
|
-
this.publishTaskProgress(parentTask.Name, 'Workflow completed', 100);
|
|
377
|
+
const eligibleTasks: TaskEntity[] = [];
|
|
333
378
|
|
|
334
|
-
|
|
379
|
+
// Check each task for incomplete dependencies
|
|
380
|
+
for (const task of tasksResult.Results) {
|
|
381
|
+
const hasIncompleteDeps = await this.hasIncompleteDependencies(task.ID);
|
|
382
|
+
if (!hasIncompleteDeps) {
|
|
383
|
+
eligibleTasks.push(task);
|
|
384
|
+
}
|
|
335
385
|
}
|
|
336
386
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
387
|
+
return eligibleTasks;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Update parent task progress based on child task completion
|
|
392
|
+
*/
|
|
393
|
+
private async updateParentTaskProgress(parentTaskId: string): Promise<void> {
|
|
394
|
+
const md = new Metadata();
|
|
395
|
+
const parentTask = await md.GetEntityObject<TaskEntity>('MJ: Tasks', this.contextUser);
|
|
396
|
+
const loaded = await parentTask.Load(parentTaskId);
|
|
397
|
+
if (!loaded) return;
|
|
398
|
+
|
|
399
|
+
const rv = new RunView();
|
|
400
|
+
|
|
401
|
+
// Get all child tasks
|
|
402
|
+
const childrenResult = await rv.RunView<TaskEntity>(
|
|
403
|
+
{
|
|
404
|
+
EntityName: 'MJ: Tasks',
|
|
405
|
+
ExtraFilter: `ParentID='${parentTaskId}'`,
|
|
406
|
+
ResultType: 'entity_object',
|
|
407
|
+
},
|
|
408
|
+
this.contextUser
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
if (!childrenResult.Success || !childrenResult.Results || childrenResult.Results.length === 0) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
353
414
|
|
|
354
|
-
|
|
415
|
+
const children = childrenResult.Results;
|
|
416
|
+
const completedCount = children.filter((t) => t.Status === 'Complete').length;
|
|
417
|
+
const totalCount = children.length;
|
|
355
418
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (!hasIncompleteDeps) {
|
|
360
|
-
eligibleTasks.push(task);
|
|
361
|
-
}
|
|
362
|
-
}
|
|
419
|
+
// Update percent complete
|
|
420
|
+
parentTask.PercentComplete = Math.round((completedCount / totalCount) * 100);
|
|
421
|
+
await parentTask.Save();
|
|
363
422
|
|
|
364
|
-
|
|
365
|
-
|
|
423
|
+
LogStatus(`Parent task ${parentTask.Name} is ${parentTask.PercentComplete}% complete (${completedCount}/${totalCount} tasks)`);
|
|
424
|
+
}
|
|
366
425
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const rv = new RunView();
|
|
377
|
-
|
|
378
|
-
// Get all child tasks
|
|
379
|
-
const childrenResult = await rv.RunView<TaskEntity>({
|
|
380
|
-
EntityName: 'MJ: Tasks',
|
|
381
|
-
ExtraFilter: `ParentID='${parentTaskId}'`,
|
|
382
|
-
ResultType: 'entity_object'
|
|
383
|
-
}, this.contextUser);
|
|
384
|
-
|
|
385
|
-
if (!childrenResult.Success || !childrenResult.Results || childrenResult.Results.length === 0) {
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
426
|
+
/**
|
|
427
|
+
* Mark parent task as complete when all children are done
|
|
428
|
+
*/
|
|
429
|
+
private async completeParentTask(parentTaskId: string): Promise<void> {
|
|
430
|
+
const md = new Metadata();
|
|
431
|
+
const parentTask = await md.GetEntityObject<TaskEntity>('MJ: Tasks', this.contextUser);
|
|
432
|
+
const loaded = await parentTask.Load(parentTaskId);
|
|
433
|
+
if (!loaded) return;
|
|
388
434
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
435
|
+
parentTask.Status = 'Complete';
|
|
436
|
+
parentTask.PercentComplete = 100;
|
|
437
|
+
parentTask.CompletedAt = new Date();
|
|
438
|
+
const saved = await parentTask.Save();
|
|
392
439
|
|
|
393
|
-
|
|
394
|
-
parentTask.PercentComplete = Math.round((completedCount / totalCount) * 100);
|
|
395
|
-
await parentTask.Save();
|
|
440
|
+
LogStatus(`Parent workflow task completed: ${parentTask.Name}`);
|
|
396
441
|
|
|
397
|
-
|
|
442
|
+
// If notifications enabled, create user notification
|
|
443
|
+
if (this.createNotifications && saved) {
|
|
444
|
+
await this.createTaskGraphCompletionNotification(parentTask);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Check if a task has incomplete dependencies
|
|
450
|
+
*/
|
|
451
|
+
private async hasIncompleteDependencies(taskId: string): Promise<boolean> {
|
|
452
|
+
const rv = new RunView();
|
|
453
|
+
|
|
454
|
+
// Get dependencies
|
|
455
|
+
const depsResult = await rv.RunView<TaskDependencyEntity>(
|
|
456
|
+
{
|
|
457
|
+
EntityName: 'MJ: Task Dependencies',
|
|
458
|
+
ExtraFilter: `TaskID='${taskId}'`,
|
|
459
|
+
ResultType: 'entity_object',
|
|
460
|
+
},
|
|
461
|
+
this.contextUser
|
|
462
|
+
);
|
|
463
|
+
|
|
464
|
+
if (!depsResult.Success || !depsResult.Results || depsResult.Results.length === 0) {
|
|
465
|
+
return false; // No dependencies
|
|
398
466
|
}
|
|
399
467
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
const loaded = await parentTask.Load(parentTaskId);
|
|
407
|
-
if (!loaded) return;
|
|
408
|
-
|
|
409
|
-
parentTask.Status = 'Complete';
|
|
410
|
-
parentTask.PercentComplete = 100;
|
|
411
|
-
parentTask.CompletedAt = new Date();
|
|
412
|
-
const saved = await parentTask.Save();
|
|
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
|
-
}
|
|
468
|
+
// Check if any dependency is not complete
|
|
469
|
+
for (const dep of depsResult.Results) {
|
|
470
|
+
const dependsOnTask = await this.loadTask(dep.DependsOnTaskID);
|
|
471
|
+
if (dependsOnTask && dependsOnTask.Status !== 'Complete') {
|
|
472
|
+
return true; // Has incomplete dependency
|
|
473
|
+
}
|
|
420
474
|
}
|
|
421
475
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Load a task by ID
|
|
481
|
+
*/
|
|
482
|
+
private async loadTask(taskId: string): Promise<TaskEntity | null> {
|
|
483
|
+
const md = new Metadata();
|
|
484
|
+
const task = await md.GetEntityObject<TaskEntity>('MJ: Tasks', this.contextUser);
|
|
485
|
+
const loaded = await task.Load(taskId);
|
|
486
|
+
return loaded ? task : null;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Execute a single task
|
|
491
|
+
*/
|
|
492
|
+
private async executeTask(task: TaskEntity): Promise<TaskExecutionResult> {
|
|
493
|
+
try {
|
|
494
|
+
LogStatus(`Executing task: ${task.Name} (${task.ID})`);
|
|
495
|
+
|
|
496
|
+
// Update status to In Progress
|
|
497
|
+
task.Status = 'In Progress';
|
|
498
|
+
task.StartedAt = new Date();
|
|
499
|
+
await task.Save();
|
|
500
|
+
|
|
501
|
+
// Load the agent entity
|
|
502
|
+
const md = new Metadata();
|
|
503
|
+
const agentEntity = await md.GetEntityObject<AIAgentEntityExtended>('AI Agents', this.contextUser);
|
|
504
|
+
const loaded = await agentEntity.Load(task.AgentID!);
|
|
505
|
+
if (!loaded) {
|
|
506
|
+
throw new Error(`Agent with ID ${task.AgentID} not found`);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Build conversation messages with task input and dependent outputs as markdown
|
|
510
|
+
const messages = await this.buildConversationMessages(task);
|
|
511
|
+
|
|
512
|
+
// Create progress callback to publish agent progress nested under task
|
|
513
|
+
const onProgress = (progress: any) => {
|
|
514
|
+
this.publishAgentProgress(task.Name, progress.step || 'processing', progress.message || '');
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
// Run the agent - use only conversationMessages, no payload parameter
|
|
518
|
+
// Payload should only be used when passing an agent its own prior output for modification
|
|
519
|
+
const agentRunner = new AgentRunner();
|
|
520
|
+
const agentResult = await agentRunner.RunAgent({
|
|
521
|
+
agent: agentEntity,
|
|
522
|
+
conversationMessages: messages,
|
|
523
|
+
contextUser: this.contextUser,
|
|
524
|
+
conversationDetailId: task.ConversationDetailID || undefined,
|
|
525
|
+
onProgress: onProgress,
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
if (agentResult.success) {
|
|
529
|
+
// Extract output - check both message and payload
|
|
530
|
+
const output = this.extractAgentOutput(agentResult);
|
|
531
|
+
|
|
532
|
+
// Store output in task metadata
|
|
533
|
+
const outputMetadata = {
|
|
534
|
+
outputType: output.type,
|
|
535
|
+
output: output.content,
|
|
536
|
+
agentRunId: agentResult.agentRun?.ID,
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// Update task with success
|
|
540
|
+
task.Status = 'Complete';
|
|
541
|
+
task.CompletedAt = new Date();
|
|
542
|
+
task.PercentComplete = 100;
|
|
543
|
+
// Store output in description (would be better as a separate column)
|
|
544
|
+
task.Description = `${task.Description}\n\n__TASK_OUTPUT__\n${JSON.stringify(outputMetadata)}`;
|
|
545
|
+
await task.Save();
|
|
546
|
+
|
|
547
|
+
LogStatus(`Task completed: ${task.Name} (output type: ${output.type})`);
|
|
548
|
+
|
|
549
|
+
// Always create artifact for task output (both message and payload results)
|
|
550
|
+
let conversationDetailId = task.ConversationDetailID;
|
|
551
|
+
if (!conversationDetailId && task.ParentID) {
|
|
552
|
+
const parentTask = await this.loadTask(task.ParentID);
|
|
553
|
+
conversationDetailId = parentTask?.ConversationDetailID || null;
|
|
437
554
|
}
|
|
438
555
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return true; // Has incomplete dependency
|
|
444
|
-
}
|
|
556
|
+
if (conversationDetailId && output.content) {
|
|
557
|
+
await this.createArtifactFromOutput(output, conversationDetailId, agentEntity, task.Name);
|
|
558
|
+
} else if (!conversationDetailId) {
|
|
559
|
+
LogError(`Cannot create artifact: No conversation detail ID found for task ${task.ID}`);
|
|
445
560
|
}
|
|
446
561
|
|
|
447
|
-
return
|
|
448
|
-
|
|
562
|
+
return {
|
|
563
|
+
taskId: task.ID,
|
|
564
|
+
success: true,
|
|
565
|
+
output: output.content,
|
|
566
|
+
};
|
|
567
|
+
} else {
|
|
568
|
+
// Update task with failure
|
|
569
|
+
task.Status = 'Failed';
|
|
570
|
+
task.CompletedAt = new Date();
|
|
571
|
+
await task.Save();
|
|
449
572
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
*/
|
|
453
|
-
private async loadTask(taskId: string): Promise<TaskEntity | null> {
|
|
454
|
-
const md = new Metadata();
|
|
455
|
-
const task = await md.GetEntityObject<TaskEntity>('MJ: Tasks', this.contextUser);
|
|
456
|
-
const loaded = await task.Load(taskId);
|
|
457
|
-
return loaded ? task : null;
|
|
458
|
-
}
|
|
573
|
+
const errorMsg = agentResult.agentRun?.ErrorMessage || 'Agent execution failed';
|
|
574
|
+
LogError(`Task failed: ${task.Name} - ${errorMsg}`);
|
|
459
575
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
// Store output in description (would be better as a separate column)
|
|
519
|
-
task.Description = `${task.Description}\n\n__TASK_OUTPUT__\n${JSON.stringify(outputMetadata)}`;
|
|
520
|
-
await task.Save();
|
|
521
|
-
|
|
522
|
-
LogStatus(`Task completed: ${task.Name} (output type: ${output.type})`);
|
|
523
|
-
|
|
524
|
-
// Always create artifact for task output (both message and payload results)
|
|
525
|
-
let conversationDetailId = task.ConversationDetailID;
|
|
526
|
-
if (!conversationDetailId && task.ParentID) {
|
|
527
|
-
const parentTask = await this.loadTask(task.ParentID);
|
|
528
|
-
conversationDetailId = parentTask?.ConversationDetailID || null;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (conversationDetailId && output.content) {
|
|
532
|
-
await this.createArtifactFromOutput(
|
|
533
|
-
output,
|
|
534
|
-
conversationDetailId,
|
|
535
|
-
agentEntity,
|
|
536
|
-
task.Name
|
|
537
|
-
);
|
|
538
|
-
} else if (!conversationDetailId) {
|
|
539
|
-
LogError(`Cannot create artifact: No conversation detail ID found for task ${task.ID}`);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
return {
|
|
543
|
-
taskId: task.ID,
|
|
544
|
-
success: true,
|
|
545
|
-
output: output.content
|
|
546
|
-
};
|
|
547
|
-
} else {
|
|
548
|
-
// Update task with failure
|
|
549
|
-
task.Status = 'Failed';
|
|
550
|
-
task.CompletedAt = new Date();
|
|
551
|
-
await task.Save();
|
|
552
|
-
|
|
553
|
-
const errorMsg = agentResult.agentRun?.ErrorMessage || 'Agent execution failed';
|
|
554
|
-
LogError(`Task failed: ${task.Name} - ${errorMsg}`);
|
|
555
|
-
|
|
556
|
-
return {
|
|
557
|
-
taskId: task.ID,
|
|
558
|
-
success: false,
|
|
559
|
-
error: errorMsg
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
} catch (error) {
|
|
563
|
-
LogError(error);
|
|
564
|
-
|
|
565
|
-
// Update task with failure
|
|
566
|
-
task.Status = 'Failed';
|
|
567
|
-
task.CompletedAt = new Date();
|
|
568
|
-
await task.Save();
|
|
569
|
-
|
|
570
|
-
return {
|
|
571
|
-
taskId: task.ID,
|
|
572
|
-
success: false,
|
|
573
|
-
error: error instanceof Error ? error.message : 'Unknown error'
|
|
574
|
-
};
|
|
575
|
-
}
|
|
576
|
+
return {
|
|
577
|
+
taskId: task.ID,
|
|
578
|
+
success: false,
|
|
579
|
+
error: errorMsg,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
} catch (error) {
|
|
583
|
+
LogError(error);
|
|
584
|
+
|
|
585
|
+
// Update task with failure
|
|
586
|
+
task.Status = 'Failed';
|
|
587
|
+
task.CompletedAt = new Date();
|
|
588
|
+
await task.Save();
|
|
589
|
+
|
|
590
|
+
return {
|
|
591
|
+
taskId: task.ID,
|
|
592
|
+
success: false,
|
|
593
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Extract input payload from task metadata
|
|
600
|
+
*/
|
|
601
|
+
private extractInputPayload(task: TaskEntity): any | null {
|
|
602
|
+
if (!task.Description) return null;
|
|
603
|
+
|
|
604
|
+
const metadataMatch = task.Description.match(/__TASK_METADATA__\n(.+?)(?:\n\n|$)/s);
|
|
605
|
+
if (!metadataMatch) return null;
|
|
606
|
+
|
|
607
|
+
try {
|
|
608
|
+
const metadata = JSON.parse(metadataMatch[1]);
|
|
609
|
+
return metadata.inputPayload || null;
|
|
610
|
+
} catch {
|
|
611
|
+
return null;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Get outputs from tasks that this task depends on
|
|
617
|
+
*/
|
|
618
|
+
private async getDependentTaskOutputs(taskId: string): Promise<Map<string, any>> {
|
|
619
|
+
const outputs = new Map<string, any>();
|
|
620
|
+
const rv = new RunView();
|
|
621
|
+
|
|
622
|
+
// Get dependencies
|
|
623
|
+
const depsResult = await rv.RunView<TaskDependencyEntity>(
|
|
624
|
+
{
|
|
625
|
+
EntityName: 'MJ: Task Dependencies',
|
|
626
|
+
ExtraFilter: `TaskID='${taskId}'`,
|
|
627
|
+
ResultType: 'entity_object',
|
|
628
|
+
},
|
|
629
|
+
this.contextUser
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
if (!depsResult.Success || !depsResult.Results) {
|
|
633
|
+
return outputs;
|
|
576
634
|
}
|
|
577
635
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
if (!task.Description) return null;
|
|
583
|
-
|
|
584
|
-
const metadataMatch = task.Description.match(/__TASK_METADATA__\n(.+?)(?:\n\n|$)/s);
|
|
585
|
-
if (!metadataMatch) return null;
|
|
636
|
+
// Get output from each dependency
|
|
637
|
+
for (const dep of depsResult.Results) {
|
|
638
|
+
const dependsOnTask = await this.loadTask(dep.DependsOnTaskID);
|
|
639
|
+
if (!dependsOnTask || !dependsOnTask.Description) continue;
|
|
586
640
|
|
|
641
|
+
const outputMatch = dependsOnTask.Description.match(/__TASK_OUTPUT__\n(.+?)$/s);
|
|
642
|
+
if (outputMatch) {
|
|
587
643
|
try {
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
} catch {
|
|
591
|
-
|
|
644
|
+
const outputMetadata = JSON.parse(outputMatch[1]);
|
|
645
|
+
outputs.set(dep.DependsOnTaskID, outputMetadata.output);
|
|
646
|
+
} catch (e) {
|
|
647
|
+
LogError(`Failed to parse output for task ${dep.DependsOnTaskID}: ${e}`);
|
|
592
648
|
}
|
|
649
|
+
}
|
|
593
650
|
}
|
|
594
651
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
*/
|
|
598
|
-
private async getDependentTaskOutputs(taskId: string): Promise<Map<string, any>> {
|
|
599
|
-
const outputs = new Map<string, any>();
|
|
600
|
-
const rv = new RunView();
|
|
601
|
-
|
|
602
|
-
// Get dependencies
|
|
603
|
-
const depsResult = await rv.RunView<TaskDependencyEntity>({
|
|
604
|
-
EntityName: 'MJ: Task Dependencies',
|
|
605
|
-
ExtraFilter: `TaskID='${taskId}'`,
|
|
606
|
-
ResultType: 'entity_object'
|
|
607
|
-
}, this.contextUser);
|
|
608
|
-
|
|
609
|
-
if (!depsResult.Success || !depsResult.Results) {
|
|
610
|
-
return outputs;
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
// Get output from each dependency
|
|
614
|
-
for (const dep of depsResult.Results) {
|
|
615
|
-
const dependsOnTask = await this.loadTask(dep.DependsOnTaskID);
|
|
616
|
-
if (!dependsOnTask || !dependsOnTask.Description) continue;
|
|
617
|
-
|
|
618
|
-
const outputMatch = dependsOnTask.Description.match(/__TASK_OUTPUT__\n(.+?)$/s);
|
|
619
|
-
if (outputMatch) {
|
|
620
|
-
try {
|
|
621
|
-
const outputMetadata = JSON.parse(outputMatch[1]);
|
|
622
|
-
outputs.set(dep.DependsOnTaskID, outputMetadata.output);
|
|
623
|
-
} catch (e) {
|
|
624
|
-
LogError(`Failed to parse output for task ${dep.DependsOnTaskID}: ${e}`);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
}
|
|
652
|
+
return outputs;
|
|
653
|
+
}
|
|
628
654
|
|
|
629
|
-
|
|
630
|
-
|
|
655
|
+
/**
|
|
656
|
+
* Build conversation messages with task input and dependent outputs formatted as markdown
|
|
657
|
+
*/
|
|
658
|
+
private async buildConversationMessages(task: TaskEntity): Promise<any[]> {
|
|
659
|
+
const messages: any[] = [];
|
|
631
660
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
*/
|
|
635
|
-
private async buildConversationMessages(task: TaskEntity): Promise<any[]> {
|
|
636
|
-
const messages: any[] = [];
|
|
637
|
-
|
|
638
|
-
// Start with task description/name as base content
|
|
639
|
-
let userContent = task.Description || task.Name;
|
|
640
|
-
|
|
641
|
-
// Extract input payload from task metadata if it exists
|
|
642
|
-
const inputPayload = this.extractInputPayload(task);
|
|
643
|
-
|
|
644
|
-
// Get dependent task outputs
|
|
645
|
-
const dependentOutputs = await this.getDependentTaskOutputs(task.ID);
|
|
646
|
-
|
|
647
|
-
// If there are dependent outputs, format them as markdown blocks
|
|
648
|
-
if (dependentOutputs.size > 0) {
|
|
649
|
-
userContent += '\n\n## Results from Dependent Tasks:\n\n';
|
|
650
|
-
for (const [taskId, outputData] of dependentOutputs.entries()) {
|
|
651
|
-
const depTask = await this.loadTask(taskId);
|
|
652
|
-
const taskName = depTask?.Name || taskId;
|
|
653
|
-
userContent += `### ${taskName}\n\`\`\`json\n${JSON.stringify(outputData, null, 2)}\n\`\`\`\n\n`;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
661
|
+
// Start with task description/name as base content
|
|
662
|
+
let userContent = task.Description || task.Name;
|
|
656
663
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
userContent += '\n\n## Task Input:\n\`\`\`json\n' + JSON.stringify(inputPayload, null, 2) + '\n\`\`\`';
|
|
660
|
-
}
|
|
664
|
+
// Extract input payload from task metadata if it exists
|
|
665
|
+
const inputPayload = this.extractInputPayload(task);
|
|
661
666
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
content: userContent
|
|
665
|
-
});
|
|
667
|
+
// Get dependent task outputs
|
|
668
|
+
const dependentOutputs = await this.getDependentTaskOutputs(task.ID);
|
|
666
669
|
|
|
667
|
-
|
|
670
|
+
// If there are dependent outputs, format them as markdown blocks
|
|
671
|
+
if (dependentOutputs.size > 0) {
|
|
672
|
+
userContent += '\n\n## Results from Dependent Tasks:\n\n';
|
|
673
|
+
for (const [taskId, outputData] of dependentOutputs.entries()) {
|
|
674
|
+
const depTask = await this.loadTask(taskId);
|
|
675
|
+
const taskName = depTask?.Name || taskId;
|
|
676
|
+
userContent += `### ${taskName}\n\`\`\`json\n${JSON.stringify(outputData, null, 2)}\n\`\`\`\n\n`;
|
|
677
|
+
}
|
|
668
678
|
}
|
|
669
679
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
// Check if agent returned a message (text response)
|
|
675
|
-
if (agentResult.agentRun?.Message) {
|
|
676
|
-
return { type: 'message', content: agentResult.agentRun.Message };
|
|
677
|
-
}
|
|
680
|
+
// If input payload exists, add it as a separate section
|
|
681
|
+
if (inputPayload) {
|
|
682
|
+
userContent += '\n\n## Task Input:\n```json\n' + JSON.stringify(inputPayload, null, 2) + '\n```';
|
|
683
|
+
}
|
|
678
684
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
685
|
+
messages.push({
|
|
686
|
+
role: 'user' as ChatMessageRole,
|
|
687
|
+
content: userContent,
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
return messages;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Extract agent output - check both message and payload
|
|
695
|
+
*/
|
|
696
|
+
private extractAgentOutput(agentResult: any): { type: 'message' | 'payload'; content: any } {
|
|
697
|
+
// Check if agent returned a message (text response)
|
|
698
|
+
if (agentResult.agentRun?.Message) {
|
|
699
|
+
return { type: 'message', content: agentResult.agentRun.Message };
|
|
700
|
+
}
|
|
683
701
|
|
|
684
|
-
|
|
685
|
-
|
|
702
|
+
// Check if agent returned a payload (structured data)
|
|
703
|
+
if (agentResult.payload && Object.keys(agentResult.payload).length > 0) {
|
|
704
|
+
return { type: 'payload', content: agentResult.payload };
|
|
686
705
|
}
|
|
687
706
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
const junction = await md.GetEntityObject<ConversationDetailArtifactEntity>(
|
|
771
|
-
'MJ: Conversation Detail Artifacts',
|
|
772
|
-
this.contextUser
|
|
773
|
-
);
|
|
774
|
-
junction.ConversationDetailID = conversationDetailId;
|
|
775
|
-
junction.ArtifactVersionID = version.ID;
|
|
776
|
-
junction.Direction = 'Output'; // Artifact produced as output from task
|
|
777
|
-
|
|
778
|
-
const junctionSaved = await junction.Save();
|
|
779
|
-
if (!junctionSaved) {
|
|
780
|
-
LogError('Failed to create artifact-conversation association');
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
LogStatus(`Linked artifact ${artifact.ID} to conversation detail ${conversationDetailId}`);
|
|
785
|
-
} catch (error) {
|
|
786
|
-
LogError(`Error creating artifact from output: ${error}`);
|
|
707
|
+
// No output
|
|
708
|
+
return { type: 'message', content: '' };
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Create artifact from task output (handles both message and payload types)
|
|
713
|
+
*/
|
|
714
|
+
private async createArtifactFromOutput(
|
|
715
|
+
output: { type: 'message' | 'payload'; content: any },
|
|
716
|
+
conversationDetailId: string,
|
|
717
|
+
agent: AIAgentEntityExtended,
|
|
718
|
+
taskName: string
|
|
719
|
+
): Promise<void> {
|
|
720
|
+
try {
|
|
721
|
+
const md = new Metadata();
|
|
722
|
+
|
|
723
|
+
// Create Artifact header
|
|
724
|
+
const artifact = await md.GetEntityObject<ArtifactEntity>('MJ: Artifacts', this.contextUser);
|
|
725
|
+
artifact.Name = `${agent.Name} - ${taskName} - ${new Date().toLocaleString()}`;
|
|
726
|
+
artifact.Description = `Artifact generated by ${agent.Name} for task: ${taskName} (${output.type})`;
|
|
727
|
+
|
|
728
|
+
// Use agent's DefaultArtifactTypeID if available, otherwise fall back to JSON
|
|
729
|
+
const defaultArtifactTypeId = (agent as any).DefaultArtifactTypeID;
|
|
730
|
+
artifact.TypeID = defaultArtifactTypeId || this.JSON_ARTIFACT_TYPE_ID;
|
|
731
|
+
|
|
732
|
+
artifact.UserID = this.contextUser.ID;
|
|
733
|
+
artifact.EnvironmentID = (this.contextUser as any).EnvironmentID || 'F51358F3-9447-4176-B313-BF8025FD8D09';
|
|
734
|
+
|
|
735
|
+
// Set visibility based on agent's ArtifactCreationMode
|
|
736
|
+
// Will compile after CodeGen adds the new fields
|
|
737
|
+
const creationMode = agent.ArtifactCreationMode;
|
|
738
|
+
if (creationMode === 'System Only') {
|
|
739
|
+
artifact.Visibility = 'System Only';
|
|
740
|
+
LogStatus(`Task artifact marked as "System Only" per agent configuration`);
|
|
741
|
+
} else {
|
|
742
|
+
artifact.Visibility = 'Always';
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const artifactSaved = await artifact.Save();
|
|
746
|
+
if (!artifactSaved) {
|
|
747
|
+
LogError('Failed to save artifact');
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
LogStatus(`Created artifact: ${artifact.Name} (${artifact.ID})`);
|
|
752
|
+
|
|
753
|
+
// Create Artifact Version with content
|
|
754
|
+
const version = await md.GetEntityObject<ArtifactVersionEntity>('MJ: Artifact Versions', this.contextUser);
|
|
755
|
+
version.ArtifactID = artifact.ID;
|
|
756
|
+
version.VersionNumber = 1;
|
|
757
|
+
|
|
758
|
+
// Store content based on output type
|
|
759
|
+
if (output.type === 'message') {
|
|
760
|
+
version.Content = output.content;
|
|
761
|
+
} else {
|
|
762
|
+
version.Content = JSON.stringify(output.content, null, 2);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
version.UserID = this.contextUser.ID;
|
|
766
|
+
|
|
767
|
+
const versionSaved = await version.Save();
|
|
768
|
+
if (!versionSaved) {
|
|
769
|
+
LogError('Failed to save artifact version');
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
LogStatus(`Created artifact version: ${version.ID}`);
|
|
774
|
+
|
|
775
|
+
// Check for extracted Name attribute and update artifact with better name
|
|
776
|
+
const nameAttr = (version as any).Attributes?.find(
|
|
777
|
+
(attr: any) => attr.StandardProperty === 'name' || attr.Name?.toLowerCase() === 'name'
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
// Check for valid name value (not null, not empty, not string "null")
|
|
781
|
+
let extractedName = nameAttr?.Value?.trim();
|
|
782
|
+
if (extractedName && extractedName.toLowerCase() !== 'null') {
|
|
783
|
+
// Strip surrounding quotes (double or single) from start and end
|
|
784
|
+
extractedName = extractedName.replace(/^["']|["']$/g, '');
|
|
785
|
+
|
|
786
|
+
artifact.Name = extractedName;
|
|
787
|
+
if (await artifact.Save()) {
|
|
788
|
+
LogStatus(`✨ Updated artifact name to: ${artifact.Name}`);
|
|
787
789
|
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// Create M2M relationship linking artifact to conversation detail
|
|
793
|
+
const junction = await md.GetEntityObject<ConversationDetailArtifactEntity>('MJ: Conversation Detail Artifacts', this.contextUser);
|
|
794
|
+
junction.ConversationDetailID = conversationDetailId;
|
|
795
|
+
junction.ArtifactVersionID = version.ID;
|
|
796
|
+
junction.Direction = 'Output'; // Artifact produced as output from task
|
|
797
|
+
|
|
798
|
+
const junctionSaved = await junction.Save();
|
|
799
|
+
if (!junctionSaved) {
|
|
800
|
+
LogError('Failed to create artifact-conversation association');
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
LogStatus(`Linked artifact ${artifact.ID} to conversation detail ${conversationDetailId}`);
|
|
805
|
+
} catch (error) {
|
|
806
|
+
LogError(`Error creating artifact from output: ${error}`);
|
|
788
807
|
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Create user notification for task graph completion
|
|
812
|
+
* Notifies user that their multi-step workflow has completed
|
|
813
|
+
*/
|
|
814
|
+
private async createTaskGraphCompletionNotification(parentTask: TaskEntity): Promise<void> {
|
|
815
|
+
try {
|
|
816
|
+
if (!parentTask.ConversationDetailID) {
|
|
817
|
+
LogStatus('Skipping notification - no conversation detail linked');
|
|
818
|
+
return;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
const md = new Metadata();
|
|
822
|
+
|
|
823
|
+
// Load conversation detail to get conversation ID
|
|
824
|
+
const detail = await md.GetEntityObject<ConversationDetailEntity>('Conversation Details', this.contextUser);
|
|
825
|
+
if (!(await detail.Load(parentTask.ConversationDetailID))) {
|
|
826
|
+
throw new Error(`Failed to load conversation detail ${parentTask.ConversationDetailID}`);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Count child tasks and success rate
|
|
830
|
+
const rv = new RunView();
|
|
831
|
+
const tasksResult = await rv.RunView<TaskEntity>(
|
|
832
|
+
{
|
|
833
|
+
EntityName: 'MJ: Tasks',
|
|
834
|
+
ExtraFilter: `ParentID='${parentTask.ID}'`,
|
|
835
|
+
ResultType: 'entity_object',
|
|
836
|
+
},
|
|
837
|
+
this.contextUser
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
const childTasks = tasksResult.Success ? tasksResult.Results || [] : [];
|
|
841
|
+
const successCount = childTasks.filter((t) => t.Status === 'Complete').length;
|
|
842
|
+
const totalCount = childTasks.length;
|
|
843
|
+
|
|
844
|
+
// Create notification
|
|
845
|
+
const notification = await md.GetEntityObject<UserNotificationEntity>('User Notifications', this.contextUser);
|
|
846
|
+
|
|
847
|
+
notification.UserID = this.contextUser.ID;
|
|
848
|
+
notification.Title = `Workflow "${parentTask.Name}" completed`;
|
|
849
|
+
notification.Message = `Your ${totalCount}-step workflow has finished. ${successCount} of ${totalCount} tasks completed successfully.`;
|
|
850
|
+
|
|
851
|
+
// Navigation configuration
|
|
852
|
+
notification.ResourceConfiguration = JSON.stringify({
|
|
853
|
+
type: 'conversation',
|
|
854
|
+
conversationId: detail.ConversationID,
|
|
855
|
+
messageId: parentTask.ConversationDetailID,
|
|
856
|
+
taskId: parentTask.ID,
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
notification.Unread = true;
|
|
860
|
+
|
|
861
|
+
if (!(await notification.Save())) {
|
|
862
|
+
throw new Error('Failed to save notification');
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
LogStatus(`📬 Created task graph notification ${notification.ID} for user ${this.contextUser.ID}`);
|
|
866
|
+
|
|
867
|
+
// Publish real-time event if pubSub available
|
|
868
|
+
if (this.pubSub && this.userPayload) {
|
|
869
|
+
this.pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
|
|
870
|
+
userPayload: JSON.stringify(this.userPayload),
|
|
871
|
+
message: JSON.stringify({
|
|
872
|
+
type: 'notification',
|
|
873
|
+
notificationId: notification.ID,
|
|
874
|
+
action: 'create',
|
|
875
|
+
title: notification.Title,
|
|
876
|
+
message: notification.Message,
|
|
877
|
+
}),
|
|
878
|
+
});
|
|
789
879
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
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
|
-
}
|
|
880
|
+
LogStatus(`📡 Published task graph notification event to client`);
|
|
881
|
+
}
|
|
882
|
+
} catch (error) {
|
|
883
|
+
LogError(`Failed to create task graph notification: ${(error as Error).message}`);
|
|
884
|
+
// Don't throw - notification failure shouldn't fail the task
|
|
870
885
|
}
|
|
886
|
+
}
|
|
871
887
|
}
|