@memberjunction/server 2.112.0 → 2.113.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.
Files changed (250) hide show
  1. package/dist/agents/skip-agent.d.ts +4 -4
  2. package/dist/agents/skip-agent.d.ts.map +1 -1
  3. package/dist/agents/skip-agent.js +951 -808
  4. package/dist/agents/skip-agent.js.map +1 -1
  5. package/dist/agents/skip-sdk.d.ts +1 -1
  6. package/dist/agents/skip-sdk.d.ts.map +1 -1
  7. package/dist/agents/skip-sdk.js +43 -53
  8. package/dist/agents/skip-sdk.js.map +1 -1
  9. package/dist/apolloServer/index.js +1 -1
  10. package/dist/auth/AuthProviderFactory.d.ts +1 -1
  11. package/dist/auth/AuthProviderFactory.d.ts.map +1 -1
  12. package/dist/auth/AuthProviderFactory.js +3 -1
  13. package/dist/auth/AuthProviderFactory.js.map +1 -1
  14. package/dist/auth/BaseAuthProvider.d.ts +1 -1
  15. package/dist/auth/BaseAuthProvider.d.ts.map +1 -1
  16. package/dist/auth/BaseAuthProvider.js +2 -3
  17. package/dist/auth/BaseAuthProvider.js.map +1 -1
  18. package/dist/auth/IAuthProvider.d.ts +1 -1
  19. package/dist/auth/IAuthProvider.d.ts.map +1 -1
  20. package/dist/auth/exampleNewUserSubClass.d.ts.map +1 -1
  21. package/dist/auth/exampleNewUserSubClass.js +1 -1
  22. package/dist/auth/exampleNewUserSubClass.js.map +1 -1
  23. package/dist/auth/index.d.ts +1 -1
  24. package/dist/auth/index.d.ts.map +1 -1
  25. package/dist/auth/index.js +6 -6
  26. package/dist/auth/index.js.map +1 -1
  27. package/dist/auth/initializeProviders.js +1 -1
  28. package/dist/auth/initializeProviders.js.map +1 -1
  29. package/dist/auth/newUsers.d.ts +1 -1
  30. package/dist/auth/newUsers.d.ts.map +1 -1
  31. package/dist/auth/newUsers.js +7 -7
  32. package/dist/auth/newUsers.js.map +1 -1
  33. package/dist/auth/providers/Auth0Provider.d.ts +1 -1
  34. package/dist/auth/providers/Auth0Provider.d.ts.map +1 -1
  35. package/dist/auth/providers/Auth0Provider.js +1 -1
  36. package/dist/auth/providers/Auth0Provider.js.map +1 -1
  37. package/dist/auth/providers/CognitoProvider.d.ts +1 -1
  38. package/dist/auth/providers/CognitoProvider.d.ts.map +1 -1
  39. package/dist/auth/providers/CognitoProvider.js +6 -3
  40. package/dist/auth/providers/CognitoProvider.js.map +1 -1
  41. package/dist/auth/providers/GoogleProvider.d.ts +1 -1
  42. package/dist/auth/providers/GoogleProvider.d.ts.map +1 -1
  43. package/dist/auth/providers/GoogleProvider.js +1 -1
  44. package/dist/auth/providers/GoogleProvider.js.map +1 -1
  45. package/dist/auth/providers/MSALProvider.d.ts +1 -1
  46. package/dist/auth/providers/MSALProvider.d.ts.map +1 -1
  47. package/dist/auth/providers/MSALProvider.js +1 -1
  48. package/dist/auth/providers/MSALProvider.js.map +1 -1
  49. package/dist/auth/providers/OktaProvider.d.ts +1 -1
  50. package/dist/auth/providers/OktaProvider.d.ts.map +1 -1
  51. package/dist/auth/providers/OktaProvider.js +1 -1
  52. package/dist/auth/providers/OktaProvider.js.map +1 -1
  53. package/dist/config.d.ts.map +1 -1
  54. package/dist/config.js +10 -22
  55. package/dist/config.js.map +1 -1
  56. package/dist/context.d.ts +1 -1
  57. package/dist/context.d.ts.map +1 -1
  58. package/dist/context.js +7 -9
  59. package/dist/context.js.map +1 -1
  60. package/dist/entitySubclasses/entityPermissions.server.d.ts +1 -1
  61. package/dist/entitySubclasses/entityPermissions.server.d.ts.map +1 -1
  62. package/dist/entitySubclasses/entityPermissions.server.js +1 -1
  63. package/dist/entitySubclasses/entityPermissions.server.js.map +1 -1
  64. package/dist/generated/generated.d.ts +788 -658
  65. package/dist/generated/generated.d.ts.map +1 -1
  66. package/dist/generated/generated.js +2050 -3054
  67. package/dist/generated/generated.js.map +1 -1
  68. package/dist/generic/KeyInputOutputTypes.d.ts +1 -1
  69. package/dist/generic/KeyInputOutputTypes.d.ts.map +1 -1
  70. package/dist/generic/KeyInputOutputTypes.js +1 -1
  71. package/dist/generic/KeyInputOutputTypes.js.map +1 -1
  72. package/dist/generic/ResolverBase.d.ts +1 -1
  73. package/dist/generic/ResolverBase.d.ts.map +1 -1
  74. package/dist/generic/ResolverBase.js +10 -15
  75. package/dist/generic/ResolverBase.js.map +1 -1
  76. package/dist/generic/RunViewResolver.d.ts +1 -1
  77. package/dist/generic/RunViewResolver.d.ts.map +1 -1
  78. package/dist/generic/RunViewResolver.js +15 -15
  79. package/dist/generic/RunViewResolver.js.map +1 -1
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +13 -18
  82. package/dist/index.js.map +1 -1
  83. package/dist/resolvers/ActionResolver.d.ts +2 -2
  84. package/dist/resolvers/ActionResolver.d.ts.map +1 -1
  85. package/dist/resolvers/ActionResolver.js +30 -28
  86. package/dist/resolvers/ActionResolver.js.map +1 -1
  87. package/dist/resolvers/AskSkipResolver.d.ts +2 -2
  88. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
  89. package/dist/resolvers/AskSkipResolver.js +50 -60
  90. package/dist/resolvers/AskSkipResolver.js.map +1 -1
  91. package/dist/resolvers/ComponentRegistryResolver.d.ts.map +1 -1
  92. package/dist/resolvers/ComponentRegistryResolver.js +38 -36
  93. package/dist/resolvers/ComponentRegistryResolver.js.map +1 -1
  94. package/dist/resolvers/CreateQueryResolver.d.ts +1 -1
  95. package/dist/resolvers/CreateQueryResolver.d.ts.map +1 -1
  96. package/dist/resolvers/CreateQueryResolver.js +40 -43
  97. package/dist/resolvers/CreateQueryResolver.js.map +1 -1
  98. package/dist/resolvers/DatasetResolver.d.ts.map +1 -1
  99. package/dist/resolvers/DatasetResolver.js +1 -1
  100. package/dist/resolvers/DatasetResolver.js.map +1 -1
  101. package/dist/resolvers/EntityRecordNameResolver.d.ts +1 -1
  102. package/dist/resolvers/EntityRecordNameResolver.d.ts.map +1 -1
  103. package/dist/resolvers/EntityRecordNameResolver.js +1 -1
  104. package/dist/resolvers/EntityRecordNameResolver.js.map +1 -1
  105. package/dist/resolvers/EntityResolver.d.ts.map +1 -1
  106. package/dist/resolvers/EntityResolver.js +1 -1
  107. package/dist/resolvers/EntityResolver.js.map +1 -1
  108. package/dist/resolvers/FileCategoryResolver.js +1 -1
  109. package/dist/resolvers/FileCategoryResolver.js.map +1 -1
  110. package/dist/resolvers/FileResolver.js +1 -1
  111. package/dist/resolvers/FileResolver.js.map +1 -1
  112. package/dist/resolvers/GetDataContextDataResolver.d.ts +1 -1
  113. package/dist/resolvers/GetDataContextDataResolver.d.ts.map +1 -1
  114. package/dist/resolvers/GetDataContextDataResolver.js +5 -5
  115. package/dist/resolvers/GetDataContextDataResolver.js.map +1 -1
  116. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  117. package/dist/resolvers/GetDataResolver.js +6 -8
  118. package/dist/resolvers/GetDataResolver.js.map +1 -1
  119. package/dist/resolvers/MergeRecordsResolver.d.ts +3 -3
  120. package/dist/resolvers/MergeRecordsResolver.d.ts.map +1 -1
  121. package/dist/resolvers/MergeRecordsResolver.js +3 -3
  122. package/dist/resolvers/MergeRecordsResolver.js.map +1 -1
  123. package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts +1 -1
  124. package/dist/resolvers/PotentialDuplicateRecordResolver.d.ts.map +1 -1
  125. package/dist/resolvers/PotentialDuplicateRecordResolver.js +1 -1
  126. package/dist/resolvers/PotentialDuplicateRecordResolver.js.map +1 -1
  127. package/dist/resolvers/QueryResolver.d.ts.map +1 -1
  128. package/dist/resolvers/QueryResolver.js +11 -11
  129. package/dist/resolvers/QueryResolver.js.map +1 -1
  130. package/dist/resolvers/ReportResolver.js +1 -1
  131. package/dist/resolvers/ReportResolver.js.map +1 -1
  132. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  133. package/dist/resolvers/RunAIAgentResolver.js +28 -27
  134. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  135. package/dist/resolvers/RunAIPromptResolver.d.ts.map +1 -1
  136. package/dist/resolvers/RunAIPromptResolver.js +31 -31
  137. package/dist/resolvers/RunAIPromptResolver.js.map +1 -1
  138. package/dist/resolvers/RunTemplateResolver.d.ts.map +1 -1
  139. package/dist/resolvers/RunTemplateResolver.js +9 -9
  140. package/dist/resolvers/RunTemplateResolver.js.map +1 -1
  141. package/dist/resolvers/SqlLoggingConfigResolver.d.ts.map +1 -1
  142. package/dist/resolvers/SqlLoggingConfigResolver.js +10 -10
  143. package/dist/resolvers/SqlLoggingConfigResolver.js.map +1 -1
  144. package/dist/resolvers/SyncDataResolver.d.ts +1 -1
  145. package/dist/resolvers/SyncDataResolver.d.ts.map +1 -1
  146. package/dist/resolvers/SyncDataResolver.js +14 -15
  147. package/dist/resolvers/SyncDataResolver.js.map +1 -1
  148. package/dist/resolvers/SyncRolesUsersResolver.d.ts +1 -1
  149. package/dist/resolvers/SyncRolesUsersResolver.d.ts.map +1 -1
  150. package/dist/resolvers/SyncRolesUsersResolver.js +44 -48
  151. package/dist/resolvers/SyncRolesUsersResolver.js.map +1 -1
  152. package/dist/resolvers/TaskResolver.d.ts.map +1 -1
  153. package/dist/resolvers/TaskResolver.js +7 -7
  154. package/dist/resolvers/TaskResolver.js.map +1 -1
  155. package/dist/resolvers/TransactionGroupResolver.d.ts +1 -1
  156. package/dist/resolvers/TransactionGroupResolver.d.ts.map +1 -1
  157. package/dist/resolvers/TransactionGroupResolver.js +12 -12
  158. package/dist/resolvers/TransactionGroupResolver.js.map +1 -1
  159. package/dist/resolvers/UserFavoriteResolver.d.ts +1 -1
  160. package/dist/resolvers/UserFavoriteResolver.d.ts.map +1 -1
  161. package/dist/resolvers/UserFavoriteResolver.js +1 -1
  162. package/dist/resolvers/UserFavoriteResolver.js.map +1 -1
  163. package/dist/resolvers/UserViewResolver.d.ts.map +1 -1
  164. package/dist/resolvers/UserViewResolver.js.map +1 -1
  165. package/dist/rest/EntityCRUDHandler.d.ts +1 -1
  166. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -1
  167. package/dist/rest/EntityCRUDHandler.js +16 -14
  168. package/dist/rest/EntityCRUDHandler.js.map +1 -1
  169. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -1
  170. package/dist/rest/RESTEndpointHandler.js +25 -23
  171. package/dist/rest/RESTEndpointHandler.js.map +1 -1
  172. package/dist/rest/ViewOperationsHandler.d.ts +1 -1
  173. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -1
  174. package/dist/rest/ViewOperationsHandler.js +21 -17
  175. package/dist/rest/ViewOperationsHandler.js.map +1 -1
  176. package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -1
  177. package/dist/scheduler/LearningCycleScheduler.js.map +1 -1
  178. package/dist/services/ScheduledJobsService.d.ts.map +1 -1
  179. package/dist/services/ScheduledJobsService.js +6 -4
  180. package/dist/services/ScheduledJobsService.js.map +1 -1
  181. package/dist/services/TaskOrchestrator.d.ts +1 -1
  182. package/dist/services/TaskOrchestrator.d.ts.map +1 -1
  183. package/dist/services/TaskOrchestrator.js +30 -30
  184. package/dist/services/TaskOrchestrator.js.map +1 -1
  185. package/dist/types.d.ts +3 -3
  186. package/dist/types.d.ts.map +1 -1
  187. package/dist/types.js +1 -0
  188. package/dist/types.js.map +1 -1
  189. package/dist/util.d.ts +1 -1
  190. package/dist/util.d.ts.map +1 -1
  191. package/dist/util.js +2 -2
  192. package/dist/util.js.map +1 -1
  193. package/package.json +39 -36
  194. package/src/agents/skip-agent.ts +1200 -1067
  195. package/src/agents/skip-sdk.ts +851 -877
  196. package/src/apolloServer/index.ts +2 -2
  197. package/src/auth/AuthProviderFactory.ts +14 -8
  198. package/src/auth/BaseAuthProvider.ts +4 -5
  199. package/src/auth/IAuthProvider.ts +2 -2
  200. package/src/auth/exampleNewUserSubClass.ts +2 -9
  201. package/src/auth/index.ts +26 -31
  202. package/src/auth/initializeProviders.ts +3 -3
  203. package/src/auth/newUsers.ts +134 -166
  204. package/src/auth/providers/Auth0Provider.ts +5 -5
  205. package/src/auth/providers/CognitoProvider.ts +10 -7
  206. package/src/auth/providers/GoogleProvider.ts +5 -4
  207. package/src/auth/providers/MSALProvider.ts +5 -5
  208. package/src/auth/providers/OktaProvider.ts +7 -6
  209. package/src/config.ts +54 -63
  210. package/src/context.ts +30 -42
  211. package/src/entitySubclasses/entityPermissions.server.ts +3 -3
  212. package/src/generated/generated.ts +40442 -48106
  213. package/src/generic/KeyInputOutputTypes.ts +6 -3
  214. package/src/generic/ResolverBase.ts +78 -119
  215. package/src/generic/RunViewResolver.ts +23 -27
  216. package/src/index.ts +48 -66
  217. package/src/resolvers/ActionResolver.ts +57 -46
  218. package/src/resolvers/AskSkipResolver.ts +533 -607
  219. package/src/resolvers/ComponentRegistryResolver.ts +562 -547
  220. package/src/resolvers/CreateQueryResolver.ts +655 -683
  221. package/src/resolvers/DatasetResolver.ts +6 -5
  222. package/src/resolvers/EntityCommunicationsResolver.ts +1 -1
  223. package/src/resolvers/EntityRecordNameResolver.ts +5 -9
  224. package/src/resolvers/EntityResolver.ts +7 -9
  225. package/src/resolvers/FileCategoryResolver.ts +2 -2
  226. package/src/resolvers/FileResolver.ts +4 -4
  227. package/src/resolvers/GetDataContextDataResolver.ts +118 -106
  228. package/src/resolvers/GetDataResolver.ts +205 -194
  229. package/src/resolvers/MergeRecordsResolver.ts +5 -5
  230. package/src/resolvers/PotentialDuplicateRecordResolver.ts +1 -1
  231. package/src/resolvers/QueryResolver.ts +78 -95
  232. package/src/resolvers/ReportResolver.ts +2 -2
  233. package/src/resolvers/RunAIAgentResolver.ts +828 -818
  234. package/src/resolvers/RunAIPromptResolver.ts +709 -693
  235. package/src/resolvers/RunTemplateResolver.ts +103 -105
  236. package/src/resolvers/SqlLoggingConfigResolver.ts +72 -69
  237. package/src/resolvers/SyncDataResolver.ts +352 -386
  238. package/src/resolvers/SyncRolesUsersResolver.ts +350 -387
  239. package/src/resolvers/TaskResolver.ts +115 -110
  240. package/src/resolvers/TransactionGroupResolver.ts +138 -143
  241. package/src/resolvers/UserFavoriteResolver.ts +8 -17
  242. package/src/resolvers/UserViewResolver.ts +12 -17
  243. package/src/rest/EntityCRUDHandler.ts +268 -291
  244. package/src/rest/RESTEndpointHandler.ts +776 -782
  245. package/src/rest/ViewOperationsHandler.ts +195 -191
  246. package/src/scheduler/LearningCycleScheduler.ts +52 -8
  247. package/src/services/ScheduledJobsService.ts +132 -129
  248. package/src/services/TaskOrchestrator.ts +776 -792
  249. package/src/types.ts +9 -15
  250. package/src/util.ts +109 -112
@@ -1,15 +1,5 @@
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';
1
+ import { Metadata, RunView, UserInfo, LogError, LogStatus } from '@memberjunction/core';
2
+ import { TaskEntity, TaskDependencyEntity, TaskTypeEntity, AIAgentEntityExtended, ConversationDetailEntity, ArtifactEntity, ArtifactVersionEntity, ConversationDetailArtifactEntity, UserNotificationEntity } from '@memberjunction/core-entities';
13
3
  import { AgentRunner } from '@memberjunction/ai-agents';
14
4
  import { ChatMessageRole } from '@memberjunction/ai';
15
5
  import { PubSubEngine } from 'type-graphql';
@@ -20,868 +10,862 @@ import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
20
10
  * Task definition from LLM response
21
11
  */
22
12
  export interface TaskDefinition {
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;
13
+ tempId: string; // LLM-generated ID for reference
14
+ name: string;
15
+ description: string;
16
+ agentName: string;
17
+ dependsOn: string[]; // Array of tempIds this task depends on
18
+ inputPayload?: any;
29
19
  }
30
20
 
31
21
  /**
32
22
  * Task graph response from Conversation Manager
33
23
  */
34
24
  export interface TaskGraphResponse {
35
- workflowName: string; // Name for the parent/workflow task
36
- tasks: TaskDefinition[];
37
- reasoning?: string;
25
+ workflowName: string; // Name for the parent/workflow task
26
+ tasks: TaskDefinition[];
27
+ reasoning?: string;
38
28
  }
39
29
 
40
30
  /**
41
31
  * Task execution result
42
32
  */
43
33
  export interface TaskExecutionResult {
44
- taskId: string;
45
- success: boolean;
46
- output?: any;
47
- error?: string;
34
+ taskId: string;
35
+ success: boolean;
36
+ output?: any;
37
+ error?: string;
48
38
  }
49
39
 
50
40
  /**
51
41
  * TaskOrchestrator handles multi-step task execution with dependencies
52
42
  */
53
43
  export class TaskOrchestrator {
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
- }
44
+ // Default artifact type ID for JSON (when agent doesn't specify DefaultArtifactTypeID)
45
+ private readonly JSON_ARTIFACT_TYPE_ID = 'ae674c7e-ea0d-49ea-89e4-0649f5eb20d4';
46
+
47
+ private taskTypeId: string | null = null;
48
+
49
+ constructor(
50
+ private contextUser: UserInfo,
51
+ private pubSub?: PubSubEngine,
52
+ private sessionId?: string,
53
+ private userPayload?: UserPayload,
54
+ private createNotifications: boolean = false
55
+ ) {}
56
+
57
+ /**
58
+ * Initialize the orchestrator by finding/creating the AI Agent Task type
59
+ */
60
+ private async ensureTaskType(): Promise<string> {
61
+ if (this.taskTypeId) {
62
+ return this.taskTypeId;
63
+ }
74
64
 
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;
88
- }
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
+ }
89
76
 
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';
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';
95
82
 
96
- const saved = await taskType.Save();
97
- if (!saved) {
98
- throw new Error('Failed to create AI Agent Execution task type');
99
- }
83
+ const saved = await taskType.Save();
84
+ if (!saved) {
85
+ throw new Error('Failed to create AI Agent Execution task type');
86
+ }
100
87
 
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');
88
+ this.taskTypeId = taskType.ID;
89
+ return this.taskTypeId;
134
90
  }
135
91
 
136
- LogStatus(`Created parent workflow task: ${parentTask.Name} (${parentTask.ID})`);
92
+ /**
93
+ * Create tasks from LLM task graph response
94
+ * @param taskGraph Task graph from Conversation Manager
95
+ * @param conversationDetailId ID of the conversation detail that triggered this
96
+ * @param environmentId Environment ID
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
+ }
122
+
123
+ LogStatus(`Created parent workflow task: ${parentTask.Name} (${parentTask.ID})`);
124
+
125
+ // Deduplicate tasks by tempId (LLM sometimes returns duplicates)
126
+ const seenTempIds = new Set<string>();
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
+ });
137
135
 
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,
136
+ LogStatus(`Creating ${uniqueTasks.length} unique child tasks (${taskGraph.tasks.length - uniqueTasks.length} duplicates filtered)`);
137
+
138
+ // Create all child tasks
139
+ for (const taskDef of uniqueTasks) {
140
+ const task = await md.GetEntityObject<TaskEntity>('MJ: Tasks', this.contextUser);
141
+
142
+ // Find agent by name
143
+ const agent = await this.findAgentByName(taskDef.agentName);
144
+ if (!agent) {
145
+ LogError(`Agent not found: ${taskDef.agentName}`);
146
+ continue;
147
+ }
148
+
149
+ task.Name = taskDef.name;
150
+ task.Description = taskDef.description;
151
+ task.TypeID = taskTypeId;
152
+ task.EnvironmentID = environmentId;
153
+ task.ParentID = parentTask.ID; // Link to parent task
154
+ task.ConversationDetailID = conversationDetailId; // Link to conversation so agent runs can be tracked
155
+ task.AgentID = agent.ID;
156
+ task.Status = 'Pending';
157
+ task.PercentComplete = 0;
158
+
159
+ // Store input payload if provided
160
+ if (taskDef.inputPayload) {
161
+ const metadata = {
162
+ inputPayload: taskDef.inputPayload,
163
+ tempId: taskDef.tempId
164
+ };
165
+ // Store in a well-known format at the end of description
166
+ task.Description = `${taskDef.description}\n\n__TASK_METADATA__\n${JSON.stringify(metadata)}`;
167
+ }
168
+
169
+ const saved = await task.Save();
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
+ }
175
+
176
+ // Create dependencies between child tasks
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
+ }
197
+
198
+ return {
199
+ parentTaskId: parentTask.ID,
200
+ taskIdMap: tempIdToRealId
177
201
  };
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
- }
187
202
  }
188
203
 
189
- // Create dependencies between child tasks
190
- for (const taskDef of uniqueTasks) {
191
- const taskId = tempIdToRealId.get(taskDef.tempId);
192
- if (!taskId) continue;
193
-
194
- for (const dependsOnTempId of taskDef.dependsOn) {
195
- const dependsOnId = tempIdToRealId.get(dependsOnTempId);
196
- if (!dependsOnId) {
197
- LogError(`Dependency not found: ${dependsOnTempId}`);
198
- continue;
204
+ /**
205
+ * Publish task progress update via PubSub
206
+ */
207
+ private publishTaskProgress(taskName: string, message: string, percentComplete: number): void {
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;
199
211
  }
200
212
 
201
- const dependency = await md.GetEntityObject<TaskDependencyEntity>('MJ: Task Dependencies', this.contextUser);
202
- dependency.TaskID = taskId;
203
- dependency.DependsOnTaskID = dependsOnId;
204
- dependency.DependencyType = 'Prerequisite';
213
+ const payload = {
214
+ message: JSON.stringify({
215
+ resolver: 'TaskOrchestrator',
216
+ type: 'TaskProgress',
217
+ status: 'ok',
218
+ data: {
219
+ taskName,
220
+ message,
221
+ percentComplete,
222
+ timestamp: new Date()
223
+ }
224
+ }),
225
+ sessionId: this.userPayload.sessionId
226
+ };
205
227
 
206
- await dependency.Save();
207
- LogStatus(`Created dependency: Task ${taskId} depends on ${dependsOnId}`);
208
- }
209
- }
228
+ LogStatus(`📡 Publishing task progress: ${taskName} - ${message} (${percentComplete}%) to session ${this.userPayload.sessionId}`);
229
+ this.pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, payload);
210
230
 
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;
231
+ LogStatus(`[Task: ${taskName}] ${message} (${percentComplete}%)`);
226
232
  }
227
233
 
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
- }
234
+ /**
235
+ * Publish agent progress update (nested within task)
236
+ */
237
+ private publishAgentProgress(taskName: string, agentStep: string, agentMessage: string): void {
238
+ if (!this.pubSub || !this.sessionId || !this.userPayload) {
239
+ LogStatus(`⚠️ PubSub not available for agent progress (pubSub: ${!!this.pubSub}, sessionId: ${!!this.sessionId}, userPayload: ${!!this.userPayload})`);
240
+ return;
241
+ }
242
+
243
+ const payload = {
244
+ message: JSON.stringify({
245
+ resolver: 'TaskOrchestrator',
246
+ type: 'AgentProgress',
247
+ status: 'ok',
248
+ data: {
249
+ taskName,
250
+ agentStep,
251
+ agentMessage,
252
+ timestamp: new Date()
253
+ }
254
+ }),
255
+ sessionId: this.userPayload.sessionId
256
+ };
257
+
258
+ LogStatus(`📡 Publishing agent progress: ${taskName} ${agentStep} to session ${this.userPayload.sessionId}`);
259
+ this.pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, payload);
259
260
 
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];
261
+ LogStatus(`[Task: ${taskName}] ${agentStep}: ${agentMessage}`);
297
262
  }
298
263
 
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);
264
+ /**
265
+ * Find agent by name
266
+ */
267
+ private async findAgentByName(agentName: string): Promise<AIAgentEntityExtended | null> {
268
+ const rv = new RunView();
269
+ const result = await rv.RunView<AIAgentEntityExtended>({
270
+ EntityName: 'AI Agents',
271
+ ExtraFilter: `Name='${agentName.replace(/'/g, "''")}'`,
272
+ ResultType: 'entity_object'
273
+ }, this.contextUser);
274
+
275
+ if (result.Success && result.Results && result.Results.length > 0) {
276
+ return result.Results[0];
341
277
  }
342
278
 
343
- // Update parent task progress after each child completes
344
- await this.updateParentTaskProgress(parentTaskId);
345
- }
279
+ return null;
346
280
  }
347
281
 
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
- }
282
+ /**
283
+ * Execute all pending tasks for a parent task, respecting dependencies
284
+ * @param parentTaskId Parent task ID
285
+ * @returns Array of execution results
286
+ */
287
+ async executeTasksForParent(parentTaskId: string): Promise<TaskExecutionResult[]> {
288
+ const results: TaskExecutionResult[] = [];
289
+ let hasMore = true;
290
+
291
+ // Get parent task for progress updates
292
+ const md = new Metadata();
293
+ const parentTask = await md.GetEntityObject<TaskEntity>('MJ: Tasks', this.contextUser);
294
+ await parentTask.Load(parentTaskId);
295
+
296
+ // Publish workflow start
297
+ this.publishTaskProgress(parentTask.Name, 'Starting workflow execution', 0);
298
+
299
+ while (hasMore) {
300
+ // Find tasks that are pending and have no incomplete dependencies
301
+ const eligibleTasks = await this.findEligibleTasks(parentTaskId);
302
+
303
+ if (eligibleTasks.length === 0) {
304
+ hasMore = false;
305
+ break;
306
+ }
307
+
308
+ // Execute eligible tasks (could be parallelized in the future)
309
+ for (const task of eligibleTasks) {
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
+ }
376
327
 
377
- const eligibleTasks: TaskEntity[] = [];
328
+ // Mark parent task as complete
329
+ await this.completeParentTask(parentTaskId);
378
330
 
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
- }
385
- }
331
+ // Publish workflow complete
332
+ this.publishTaskProgress(parentTask.Name, 'Workflow completed', 100);
386
333
 
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;
334
+ return results;
413
335
  }
414
336
 
415
- const children = childrenResult.Results;
416
- const completedCount = children.filter((t) => t.Status === 'Complete').length;
417
- const totalCount = children.length;
337
+ /**
338
+ * Find tasks that are ready to execute (pending with no incomplete dependencies)
339
+ */
340
+ private async findEligibleTasks(parentTaskId: string): Promise<TaskEntity[]> {
341
+ const rv = new RunView();
342
+
343
+ // Get all pending tasks for this parent
344
+ const tasksResult = await rv.RunView<TaskEntity>({
345
+ EntityName: 'MJ: Tasks',
346
+ ExtraFilter: `ParentID='${parentTaskId}' AND Status='Pending'`,
347
+ ResultType: 'entity_object'
348
+ }, this.contextUser);
349
+
350
+ if (!tasksResult.Success || !tasksResult.Results) {
351
+ return [];
352
+ }
418
353
 
419
- // Update percent complete
420
- parentTask.PercentComplete = Math.round((completedCount / totalCount) * 100);
421
- await parentTask.Save();
354
+ const eligibleTasks: TaskEntity[] = [];
422
355
 
423
- LogStatus(`Parent task ${parentTask.Name} is ${parentTask.PercentComplete}% complete (${completedCount}/${totalCount} tasks)`);
424
- }
356
+ // Check each task for incomplete dependencies
357
+ for (const task of tasksResult.Results) {
358
+ const hasIncompleteDeps = await this.hasIncompleteDependencies(task.ID);
359
+ if (!hasIncompleteDeps) {
360
+ eligibleTasks.push(task);
361
+ }
362
+ }
425
363
 
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;
364
+ return eligibleTasks;
365
+ }
434
366
 
435
- parentTask.Status = 'Complete';
436
- parentTask.PercentComplete = 100;
437
- parentTask.CompletedAt = new Date();
438
- const saved = await parentTask.Save();
367
+ /**
368
+ * Update parent task progress based on child task completion
369
+ */
370
+ private async updateParentTaskProgress(parentTaskId: string): Promise<void> {
371
+ const md = new Metadata();
372
+ const parentTask = await md.GetEntityObject<TaskEntity>('MJ: Tasks', this.contextUser);
373
+ const loaded = await parentTask.Load(parentTaskId);
374
+ if (!loaded) return;
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
+ }
439
388
 
440
- LogStatus(`Parent workflow task completed: ${parentTask.Name}`);
389
+ const children = childrenResult.Results;
390
+ const completedCount = children.filter(t => t.Status === 'Complete').length;
391
+ const totalCount = children.length;
441
392
 
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
466
- }
393
+ // Update percent complete
394
+ parentTask.PercentComplete = Math.round((completedCount / totalCount) * 100);
395
+ await parentTask.Save();
467
396
 
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
- }
397
+ LogStatus(`Parent task ${parentTask.Name} is ${parentTask.PercentComplete}% complete (${completedCount}/${totalCount} tasks)`);
474
398
  }
475
399
 
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;
400
+ /**
401
+ * Mark parent task as complete when all children are done
402
+ */
403
+ private async completeParentTask(parentTaskId: string): Promise<void> {
404
+ const md = new Metadata();
405
+ const parentTask = await md.GetEntityObject<TaskEntity>('MJ: Tasks', this.contextUser);
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);
554
419
  }
420
+ }
555
421
 
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}`);
422
+ /**
423
+ * Check if a task has incomplete dependencies
424
+ */
425
+ private async hasIncompleteDependencies(taskId: string): Promise<boolean> {
426
+ const rv = new RunView();
427
+
428
+ // Get dependencies
429
+ const depsResult = await rv.RunView<TaskDependencyEntity>({
430
+ EntityName: 'MJ: Task Dependencies',
431
+ ExtraFilter: `TaskID='${taskId}'`,
432
+ ResultType: 'entity_object'
433
+ }, this.contextUser);
434
+
435
+ if (!depsResult.Success || !depsResult.Results || depsResult.Results.length === 0) {
436
+ return false; // No dependencies
560
437
  }
561
438
 
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();
572
-
573
- const errorMsg = agentResult.agentRun?.ErrorMessage || 'Agent execution failed';
574
- LogError(`Task failed: ${task.Name} - ${errorMsg}`);
439
+ // Check if any dependency is not complete
440
+ for (const dep of depsResult.Results) {
441
+ const dependsOnTask = await this.loadTask(dep.DependsOnTaskID);
442
+ if (dependsOnTask && dependsOnTask.Status !== 'Complete') {
443
+ return true; // Has incomplete dependency
444
+ }
445
+ }
575
446
 
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
- };
447
+ return false;
595
448
  }
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;
449
+
450
+ /**
451
+ * Load a task by ID
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;
612
458
  }
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;
459
+
460
+ /**
461
+ * Execute a single task
462
+ */
463
+ private async executeTask(task: TaskEntity): Promise<TaskExecutionResult> {
464
+ try {
465
+ LogStatus(`Executing task: ${task.Name} (${task.ID})`);
466
+
467
+ // Update status to In Progress
468
+ task.Status = 'In Progress';
469
+ task.StartedAt = new Date();
470
+ await task.Save();
471
+
472
+ // Load the agent entity
473
+ const md = new Metadata();
474
+ const agentEntity = await md.GetEntityObject<AIAgentEntityExtended>('AI Agents', this.contextUser);
475
+ const loaded = await agentEntity.Load(task.AgentID!);
476
+ if (!loaded) {
477
+ throw new Error(`Agent with ID ${task.AgentID} not found`);
478
+ }
479
+
480
+ // Build conversation messages with task input and dependent outputs as markdown
481
+ const messages = await this.buildConversationMessages(task);
482
+
483
+ // Create progress callback to publish agent progress nested under task
484
+ const onProgress = (progress: any) => {
485
+ this.publishAgentProgress(
486
+ task.Name,
487
+ progress.step || 'processing',
488
+ progress.message || ''
489
+ );
490
+ };
491
+
492
+ // Run the agent - use only conversationMessages, no payload parameter
493
+ // Payload should only be used when passing an agent its own prior output for modification
494
+ const agentRunner = new AgentRunner();
495
+ const agentResult = await agentRunner.RunAgent({
496
+ agent: agentEntity,
497
+ conversationMessages: messages,
498
+ contextUser: this.contextUser,
499
+ conversationDetailId: task.ConversationDetailID || undefined,
500
+ onProgress: onProgress
501
+ });
502
+
503
+ if (agentResult.success) {
504
+ // Extract output - check both message and payload
505
+ const output = this.extractAgentOutput(agentResult);
506
+
507
+ // Store output in task metadata
508
+ const outputMetadata = {
509
+ outputType: output.type,
510
+ output: output.content,
511
+ agentRunId: agentResult.agentRun?.ID
512
+ };
513
+
514
+ // Update task with success
515
+ task.Status = 'Complete';
516
+ task.CompletedAt = new Date();
517
+ task.PercentComplete = 100;
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
+ }
634
576
  }
635
577
 
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;
578
+ /**
579
+ * Extract input payload from task metadata
580
+ */
581
+ private extractInputPayload(task: TaskEntity): any | null {
582
+ if (!task.Description) return null;
583
+
584
+ const metadataMatch = task.Description.match(/__TASK_METADATA__\n(.+?)(?:\n\n|$)/s);
585
+ if (!metadataMatch) return null;
640
586
 
641
- const outputMatch = dependsOnTask.Description.match(/__TASK_OUTPUT__\n(.+?)$/s);
642
- if (outputMatch) {
643
587
  try {
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}`);
588
+ const metadata = JSON.parse(metadataMatch[1]);
589
+ return metadata.inputPayload || null;
590
+ } catch {
591
+ return null;
648
592
  }
649
- }
650
593
  }
651
594
 
652
- return outputs;
653
- }
595
+ /**
596
+ * Get outputs from tasks that this task depends on
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
+ }
654
612
 
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[] = [];
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
+ }
660
628
 
661
- // Start with task description/name as base content
662
- let userContent = task.Description || task.Name;
629
+ return outputs;
630
+ }
663
631
 
664
- // Extract input payload from task metadata if it exists
665
- const inputPayload = this.extractInputPayload(task);
632
+ /**
633
+ * Build conversation messages with task input and dependent outputs formatted as markdown
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
+ }
666
656
 
667
- // Get dependent task outputs
668
- const dependentOutputs = await this.getDependentTaskOutputs(task.ID);
657
+ // If input payload exists, add it as a separate section
658
+ if (inputPayload) {
659
+ userContent += '\n\n## Task Input:\n\`\`\`json\n' + JSON.stringify(inputPayload, null, 2) + '\n\`\`\`';
660
+ }
669
661
 
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
- }
678
- }
662
+ messages.push({
663
+ role: 'user' as ChatMessageRole,
664
+ content: userContent
665
+ });
679
666
 
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```';
667
+ return messages;
683
668
  }
684
669
 
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
- }
670
+ /**
671
+ * Extract agent output - check both message and payload
672
+ */
673
+ private extractAgentOutput(agentResult: any): { type: 'message' | 'payload', content: any } {
674
+ // Check if agent returned a message (text response)
675
+ if (agentResult.agentRun?.Message) {
676
+ return { type: 'message', content: agentResult.agentRun.Message };
677
+ }
678
+
679
+ // Check if agent returned a payload (structured data)
680
+ if (agentResult.payload && Object.keys(agentResult.payload).length > 0) {
681
+ return { type: 'payload', content: agentResult.payload };
682
+ }
701
683
 
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 };
684
+ // No output
685
+ return { type: 'message', content: '' };
705
686
  }
706
687
 
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}`);
688
+ /**
689
+ * Create artifact from task output (handles both message and payload types)
690
+ */
691
+ private async createArtifactFromOutput(
692
+ output: { type: 'message' | 'payload', content: any },
693
+ conversationDetailId: string,
694
+ agent: AIAgentEntityExtended,
695
+ taskName: string
696
+ ): Promise<void> {
697
+ try {
698
+ const md = new Metadata();
699
+
700
+ // Create Artifact header
701
+ const artifact = await md.GetEntityObject<ArtifactEntity>('MJ: Artifacts', this.contextUser);
702
+ artifact.Name = `${agent.Name} - ${taskName} - ${new Date().toLocaleString()}`;
703
+ artifact.Description = `Artifact generated by ${agent.Name} for task: ${taskName} (${output.type})`;
704
+
705
+ // Use agent's DefaultArtifactTypeID if available, otherwise fall back to JSON
706
+ const defaultArtifactTypeId = (agent as any).DefaultArtifactTypeID;
707
+ artifact.TypeID = defaultArtifactTypeId || this.JSON_ARTIFACT_TYPE_ID;
708
+
709
+ artifact.UserID = this.contextUser.ID;
710
+ artifact.EnvironmentID = (this.contextUser as any).EnvironmentID || 'F51358F3-9447-4176-B313-BF8025FD8D09';
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
+
722
+ const artifactSaved = await artifact.Save();
723
+ if (!artifactSaved) {
724
+ LogError('Failed to save artifact');
725
+ return;
726
+ }
727
+
728
+ LogStatus(`Created artifact: ${artifact.Name} (${artifact.ID})`);
729
+
730
+ // Create Artifact Version with content
731
+ const version = await md.GetEntityObject<ArtifactVersionEntity>('MJ: Artifact Versions', this.contextUser);
732
+ version.ArtifactID = artifact.ID;
733
+ version.VersionNumber = 1;
734
+
735
+ // Store content based on output type
736
+ if (output.type === 'message') {
737
+ version.Content = output.content;
738
+ } else {
739
+ version.Content = JSON.stringify(output.content, null, 2);
740
+ }
741
+
742
+ version.UserID = this.contextUser.ID;
743
+
744
+ const versionSaved = await version.Save();
745
+ if (!versionSaved) {
746
+ LogError('Failed to save artifact version');
747
+ return;
748
+ }
749
+
750
+ LogStatus(`Created artifact version: ${version.ID}`);
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
+
769
+ // Create M2M relationship linking artifact to conversation detail
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}`);
789
787
  }
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}`);
807
788
  }
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
- });
879
789
 
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
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
+ }
885
870
  }
886
- }
887
871
  }