@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.
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 +808 -951
  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 +53 -43
  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 +1 -3
  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 +3 -2
  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 +3 -6
  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 +22 -10
  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 +9 -7
  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 +648 -648
  65. package/dist/generated/generated.d.ts.map +1 -1
  66. package/dist/generated/generated.js +2986 -1133
  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 +15 -10
  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 +18 -9
  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 +28 -30
  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 +60 -50
  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 +36 -38
  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 +43 -40
  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 +8 -6
  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 +27 -28
  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 +15 -14
  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 +48 -44
  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 +14 -16
  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 +23 -25
  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 +17 -21
  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 +4 -6
  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 +0 -1
  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 +36 -37
  194. package/src/agents/skip-agent.ts +1067 -1200
  195. package/src/agents/skip-sdk.ts +877 -851
  196. package/src/apolloServer/index.ts +2 -2
  197. package/src/auth/AuthProviderFactory.ts +8 -14
  198. package/src/auth/BaseAuthProvider.ts +5 -4
  199. package/src/auth/IAuthProvider.ts +2 -2
  200. package/src/auth/exampleNewUserSubClass.ts +9 -2
  201. package/src/auth/index.ts +31 -26
  202. package/src/auth/initializeProviders.ts +3 -3
  203. package/src/auth/newUsers.ts +166 -134
  204. package/src/auth/providers/Auth0Provider.ts +5 -5
  205. package/src/auth/providers/CognitoProvider.ts +7 -10
  206. package/src/auth/providers/GoogleProvider.ts +4 -5
  207. package/src/auth/providers/MSALProvider.ts +5 -5
  208. package/src/auth/providers/OktaProvider.ts +6 -7
  209. package/src/config.ts +63 -54
  210. package/src/context.ts +42 -30
  211. package/src/entitySubclasses/entityPermissions.server.ts +3 -3
  212. package/src/generated/generated.ts +48130 -39930
  213. package/src/generic/KeyInputOutputTypes.ts +3 -6
  214. package/src/generic/ResolverBase.ts +119 -78
  215. package/src/generic/RunViewResolver.ts +27 -23
  216. package/src/index.ts +66 -42
  217. package/src/resolvers/ActionResolver.ts +46 -57
  218. package/src/resolvers/AskSkipResolver.ts +607 -533
  219. package/src/resolvers/ComponentRegistryResolver.ts +547 -562
  220. package/src/resolvers/CreateQueryResolver.ts +683 -655
  221. package/src/resolvers/DatasetResolver.ts +5 -6
  222. package/src/resolvers/EntityCommunicationsResolver.ts +1 -1
  223. package/src/resolvers/EntityRecordNameResolver.ts +9 -5
  224. package/src/resolvers/EntityResolver.ts +9 -7
  225. package/src/resolvers/FileCategoryResolver.ts +2 -2
  226. package/src/resolvers/FileResolver.ts +4 -4
  227. package/src/resolvers/GetDataContextDataResolver.ts +106 -118
  228. package/src/resolvers/GetDataResolver.ts +194 -205
  229. package/src/resolvers/MergeRecordsResolver.ts +5 -5
  230. package/src/resolvers/PotentialDuplicateRecordResolver.ts +1 -1
  231. package/src/resolvers/QueryResolver.ts +95 -78
  232. package/src/resolvers/ReportResolver.ts +2 -2
  233. package/src/resolvers/RunAIAgentResolver.ts +818 -828
  234. package/src/resolvers/RunAIPromptResolver.ts +693 -709
  235. package/src/resolvers/RunTemplateResolver.ts +105 -103
  236. package/src/resolvers/SqlLoggingConfigResolver.ts +69 -72
  237. package/src/resolvers/SyncDataResolver.ts +386 -352
  238. package/src/resolvers/SyncRolesUsersResolver.ts +387 -350
  239. package/src/resolvers/TaskResolver.ts +110 -115
  240. package/src/resolvers/TransactionGroupResolver.ts +143 -138
  241. package/src/resolvers/UserFavoriteResolver.ts +17 -8
  242. package/src/resolvers/UserViewResolver.ts +17 -12
  243. package/src/rest/EntityCRUDHandler.ts +291 -268
  244. package/src/rest/RESTEndpointHandler.ts +782 -776
  245. package/src/rest/ViewOperationsHandler.ts +191 -195
  246. package/src/scheduler/LearningCycleScheduler.ts +8 -52
  247. package/src/services/ScheduledJobsService.ts +129 -132
  248. package/src/services/TaskOrchestrator.ts +792 -776
  249. package/src/types.ts +15 -9
  250. package/src/util.ts +112 -109
@@ -1,5 +1,18 @@
1
1
  import { Arg, Ctx, Field, InputType, Mutation, ObjectType, PubSub, PubSubEngine, Query, Resolver } from 'type-graphql';
2
- import { LogError, LogStatus, Metadata, RunView, UserInfo, CompositeKey, EntityFieldInfo, EntityInfo, EntityRelationshipInfo, EntitySaveOptions, EntityDeleteOptions, IMetadataProvider } from '@memberjunction/core';
2
+ import {
3
+ LogError,
4
+ LogStatus,
5
+ Metadata,
6
+ RunView,
7
+ UserInfo,
8
+ CompositeKey,
9
+ EntityFieldInfo,
10
+ EntityInfo,
11
+ EntityRelationshipInfo,
12
+ EntitySaveOptions,
13
+ EntityDeleteOptions,
14
+ IMetadataProvider,
15
+ } from '@memberjunction/global';
3
16
  import { AppContext, UserPayload, MJ_SERVER_EVENT_CODE } from '../types.js';
4
17
  import { BehaviorSubject } from 'rxjs';
5
18
  import { take } from 'rxjs/operators';
@@ -52,9 +65,18 @@ import {
52
65
  DataContextEntity,
53
66
  DataContextItemEntity,
54
67
  UserNotificationEntity,
55
- AIAgentEntityExtended
68
+ AIAgentEntityExtended,
56
69
  } from '@memberjunction/core-entities';
57
- import { apiKey as callbackAPIKey, AskSkipInfo, baseUrl, publicUrl, configInfo, graphqlPort, graphqlRootPath, mj_core_schema } from '../config.js';
70
+ import {
71
+ apiKey as callbackAPIKey,
72
+ AskSkipInfo,
73
+ baseUrl,
74
+ publicUrl,
75
+ configInfo,
76
+ graphqlPort,
77
+ graphqlRootPath,
78
+ mj_core_schema,
79
+ } from '../config.js';
58
80
  import mssql from 'mssql';
59
81
 
60
82
  import { registerEnumType } from 'type-graphql';
@@ -84,12 +106,15 @@ const SKIP_API_ENDPOINTS = {
84
106
  */
85
107
  class ActiveConversationStreams {
86
108
  private static instance: ActiveConversationStreams;
87
- private streams: Map<string, {
88
- lastStatus: string,
89
- lastUpdate: Date,
90
- startTime: Date, // When processing actually started
91
- sessionIds: Set<string> // Track which sessions are listening
92
- }> = new Map();
109
+ private streams: Map<
110
+ string,
111
+ {
112
+ lastStatus: string;
113
+ lastUpdate: Date;
114
+ startTime: Date; // When processing actually started
115
+ sessionIds: Set<string>; // Track which sessions are listening
116
+ }
117
+ > = new Map();
93
118
 
94
119
  private constructor() {}
95
120
 
@@ -114,7 +139,7 @@ class ActiveConversationStreams {
114
139
  lastStatus: status,
115
140
  lastUpdate: now,
116
141
  startTime: now, // Track when processing started
117
- sessionIds: sessionId ? new Set([sessionId]) : new Set()
142
+ sessionIds: sessionId ? new Set([sessionId]) : new Set(),
118
143
  });
119
144
  }
120
145
  }
@@ -140,7 +165,7 @@ class ActiveConversationStreams {
140
165
  lastStatus: 'Processing...',
141
166
  lastUpdate: now,
142
167
  startTime: now, // Track when processing started
143
- sessionIds: new Set([sessionId])
168
+ sessionIds: new Set([sessionId]),
144
169
  });
145
170
  }
146
171
  }
@@ -152,7 +177,7 @@ class ActiveConversationStreams {
152
177
  isActive(conversationId: string): boolean {
153
178
  const stream = this.streams.get(conversationId);
154
179
  if (!stream) return false;
155
-
180
+
156
181
  // Consider a stream inactive if no update in last 5 minutes
157
182
  const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
158
183
  return stream.lastUpdate > fiveMinutesAgo;
@@ -170,19 +195,19 @@ class ActiveConversationStreams {
170
195
  cleanupStaleStreams() {
171
196
  const now = new Date();
172
197
  const staleThreshold = new Date(now.getTime() - 30 * 60 * 1000); // 30 minutes
173
-
198
+
174
199
  const staleConversations: string[] = [];
175
200
  this.streams.forEach((stream, conversationId) => {
176
201
  if (stream.lastUpdate < staleThreshold) {
177
202
  staleConversations.push(conversationId);
178
203
  }
179
204
  });
180
-
181
- staleConversations.forEach(conversationId => {
205
+
206
+ staleConversations.forEach((conversationId) => {
182
207
  this.streams.delete(conversationId);
183
208
  LogStatus(`Cleaned up stale stream for conversation ${conversationId}`);
184
209
  });
185
-
210
+
186
211
  if (staleConversations.length > 0) {
187
212
  LogStatus(`Cleaned up ${staleConversations.length} stale conversation streams`);
188
213
  }
@@ -192,9 +217,12 @@ class ActiveConversationStreams {
192
217
  const activeStreams = ActiveConversationStreams.getInstance();
193
218
 
194
219
  // Set up periodic cleanup of stale streams (every 10 minutes)
195
- setInterval(() => {
196
- activeStreams.cleanupStaleStreams();
197
- }, 10 * 60 * 1000);
220
+ setInterval(
221
+ () => {
222
+ activeStreams.cleanupStaleStreams();
223
+ },
224
+ 10 * 60 * 1000
225
+ );
198
226
 
199
227
  @ObjectType()
200
228
  class ReattachConversationResponse {
@@ -283,11 +311,11 @@ export class CycleDetailsType {
283
311
  /** Unique identifier for the learning cycle */
284
312
  @Field(() => String)
285
313
  LearningCycleId: string;
286
-
314
+
287
315
  /** ISO timestamp when the cycle started */
288
316
  @Field(() => String)
289
317
  StartTime: string;
290
-
318
+
291
319
  /** Duration of the cycle in minutes */
292
320
  @Field(() => Number)
293
321
  RunningForMinutes: number;
@@ -302,15 +330,15 @@ export class RunningOrganizationType {
302
330
  /** Identifier of the organization running the cycle */
303
331
  @Field(() => String)
304
332
  OrganizationId: string;
305
-
333
+
306
334
  /** Unique identifier for the learning cycle */
307
335
  @Field(() => String)
308
336
  LearningCycleId: string;
309
-
337
+
310
338
  /** ISO timestamp when the cycle started */
311
339
  @Field(() => String)
312
340
  StartTime: string;
313
-
341
+
314
342
  /** Duration the cycle has been running in minutes */
315
343
  @Field(() => Number)
316
344
  RunningForMinutes: number;
@@ -325,11 +353,11 @@ export class LearningCycleStatusType {
325
353
  /** Whether the scheduler process is currently active */
326
354
  @Field(() => Boolean)
327
355
  IsSchedulerRunning: boolean;
328
-
356
+
329
357
  /** ISO timestamp of the last time the scheduler ran a cycle */
330
358
  @Field(() => String, { nullable: true })
331
359
  LastRunTime: string;
332
-
360
+
333
361
  /** List of organizations that are currently running learning cycles */
334
362
  @Field(() => [RunningOrganizationType], { nullable: true })
335
363
  RunningOrganizations: RunningOrganizationType[];
@@ -344,15 +372,15 @@ export class StopLearningCycleResultType {
344
372
  /** Whether the stop operation succeeded */
345
373
  @Field(() => Boolean)
346
374
  Success: boolean;
347
-
375
+
348
376
  /** Descriptive message about the result of the stop operation */
349
377
  @Field(() => String)
350
378
  Message: string;
351
-
379
+
352
380
  /** Whether the cycle was actually running when the stop was attempted */
353
381
  @Field(() => Boolean)
354
382
  WasRunning: boolean;
355
-
383
+
356
384
  /** Details about the cycle that was stopped (if any) */
357
385
  @Field(() => CycleDetailsType, { nullable: true })
358
386
  CycleDetails: CycleDetailsType;
@@ -379,7 +407,7 @@ export class StopLearningCycleResultType {
379
407
  // // LogStatus('Skip AI Learning Cycles not enabled in configuration');
380
408
  // return;
381
409
  // }
382
-
410
+
383
411
  // // Check if we have a valid endpoint when cycles are enabled
384
412
  // const hasLearningEndpoint = (skipConfigInfo.url && skipConfigInfo.url.trim().length > 0) ||
385
413
  // (skipConfigInfo.learningCycleURL && skipConfigInfo.learningCycleURL.trim().length > 0);
@@ -387,19 +415,18 @@ export class StopLearningCycleResultType {
387
415
  // LogError('Skip AI Learning cycle scheduler not started: Learning cycles are enabled but no Learning Cycle API endpoint is configured');
388
416
  // return;
389
417
  // }
390
-
418
+
391
419
  // const dataSources = event.args.dataSources;
392
420
  // if (dataSources && dataSources.length > 0) {
393
421
  // // Initialize the scheduler
394
422
  // const scheduler = LearningCycleScheduler.Instance;
395
-
423
+
396
424
  // // Set the data sources for the scheduler
397
425
  // scheduler.setDataSources(dataSources);
398
-
426
+
399
427
  // // Default is 60 minutes, if the interval is not set in the config, use 60 minutes
400
428
  // const interval = skipConfigInfo.learningCycleIntervalInMinutes ?? 60;
401
429
 
402
-
403
430
  // if (skipConfigInfo.learningCycleRunUponStartup) {
404
431
  // // If configured to run immediately, run the learning cycle
405
432
  // LogStatus('Skip API Learning Cycle: Run Upon Startup is enabled, running learning cycle immediately');
@@ -439,32 +466,32 @@ export class StopLearningCycleResultType {
439
466
  */
440
467
  type BaseSkipRequest = {
441
468
  /** Entity metadata to send to Skip */
442
- entities: SkipEntityInfo[],
469
+ entities: SkipEntityInfo[];
443
470
  /** Query metadata to send to Skip */
444
- queries: SkipQueryInfo[],
471
+ queries: SkipQueryInfo[];
445
472
  /** Agent notes to send to Skip */
446
- notes: SkipAPIAgentNote[],
473
+ notes: SkipAPIAgentNote[];
447
474
  /** Note type definitions to send to Skip */
448
- noteTypes: SkipAPIAgentNoteType[],
475
+ noteTypes: SkipAPIAgentNoteType[];
449
476
  /** Agent requests to send to Skip */
450
- requests: SkipAPIAgentRequest[],
477
+ requests: SkipAPIAgentRequest[];
451
478
  /** Access token for authorizing Skip to call back to MemberJunction */
452
- accessToken: GetDataAccessToken,
479
+ accessToken: GetDataAccessToken;
453
480
  /** Organization identifier */
454
- organizationID: string,
481
+ organizationID: string;
455
482
  /** Additional organization-specific information */
456
- organizationInfo: any,
483
+ organizationInfo: any;
457
484
  /** API keys for various AI services to be used by Skip */
458
- apiKeys: SkipAPIRequestAPIKey[],
485
+ apiKeys: SkipAPIRequestAPIKey[];
459
486
  /** URL of the calling server for callback purposes */
460
- callingServerURL: string,
487
+ callingServerURL: string;
461
488
  /** API key for the calling server */
462
- callingServerAPIKey: string,
489
+ callingServerAPIKey: string;
463
490
  /** Access token for the calling server */
464
- callingServerAccessToken: string,
491
+ callingServerAccessToken: string;
465
492
  /** Email of the user making the request */
466
- userEmail: string
467
- }
493
+ userEmail: string;
494
+ };
468
495
  /**
469
496
  * Resolver for Skip AI interactions
470
497
  * Handles conversations with Skip, learning cycles, and related operations.
@@ -474,14 +501,14 @@ type BaseSkipRequest = {
474
501
  export class AskSkipResolver {
475
502
  /** Default name for new conversations */
476
503
  private static _defaultNewChatName = 'New Chat';
477
-
504
+
478
505
  /** Maximum number of historical messages to include in a conversation context */
479
506
  private static _maxHistoricalMessages = 30;
480
507
 
481
508
  /**
482
509
  * Handles a chat interaction with Skip about a specific data record
483
510
  * Allows users to ask questions about a particular entity record
484
- *
511
+ *
485
512
  * @param UserQuestion The question or message from the user
486
513
  * @param ConversationId ID of an existing conversation, or empty for a new conversation
487
514
  * @param EntityName The name of the entity the record belongs to
@@ -509,11 +536,7 @@ export class AskSkipResolver {
509
536
  // now load up the messages. We will load up ALL of the messages for this conversation, and then pass them to the Skip API
510
537
  let messages: SkipMessage[] = [];
511
538
  if (ConversationId && ConversationId.length > 0) {
512
- messages = await this.LoadConversationDetailsIntoSkipMessages(
513
- dataSource,
514
- ConversationId,
515
- AskSkipResolver._maxHistoricalMessages
516
- );
539
+ messages = await this.LoadConversationDetailsIntoSkipMessages(dataSource, ConversationId, AskSkipResolver._maxHistoricalMessages);
517
540
  }
518
541
 
519
542
  const md = GetReadWriteProvider(providers);
@@ -555,7 +578,20 @@ export class AskSkipResolver {
555
578
  }
556
579
  }
557
580
 
558
- const input = await this.buildSkipChatAPIRequest(messages, ConversationId, dataContext, 'chat_with_a_record', false, false, false, false, user, dataSource, false, false);
581
+ const input = await this.buildSkipChatAPIRequest(
582
+ messages,
583
+ ConversationId,
584
+ dataContext,
585
+ 'chat_with_a_record',
586
+ false,
587
+ false,
588
+ false,
589
+ false,
590
+ user,
591
+ dataSource,
592
+ false,
593
+ false
594
+ );
559
595
  messages.push({
560
596
  content: UserQuestion,
561
597
  role: 'user',
@@ -568,7 +604,7 @@ export class AskSkipResolver {
568
604
  // /**
569
605
  // * Executes a Skip learning cycle
570
606
  // * Learning cycles allow Skip to analyze conversations and improve its knowledge and capabilities
571
- // *
607
+ // *
572
608
  // * @param dataSource Database connection
573
609
  // * @param userPayload Information about the authenticated user
574
610
  // * @param ForceEntityRefresh Whether to force a refresh of entity metadata
@@ -591,7 +627,7 @@ export class AskSkipResolver {
591
627
  // requestChanges: []
592
628
  // };
593
629
  // }
594
-
630
+
595
631
  // // Check if we have a valid endpoint when cycles are enabled
596
632
  // const hasLearningEndpoint = (skipConfigInfo.url && skipConfigInfo.url.trim().length > 0) ||
597
633
  // (skipConfigInfo.learningCycleURL && skipConfigInfo.learningCycleURL.trim().length > 0);
@@ -605,20 +641,20 @@ export class AskSkipResolver {
605
641
  // requestChanges: []
606
642
  // };
607
643
  // }
608
-
644
+
609
645
  // const startTime = new Date();
610
646
  // // First, get the user from the cache
611
647
  // const user = UserCache.Instance.Users.find((u) => u.Email.trim().toLowerCase() === userPayload.email.trim().toLowerCase());
612
648
  // if (!user) throw new Error(`User ${userPayload.email} not found in UserCache`);
613
649
 
614
650
  // // if already configured this does nothing, just makes sure we're configured
615
- // await AIEngine.Instance.Config(false, user);
651
+ // await AIEngine.Instance.Config(false, user);
616
652
 
617
653
  // // Check if this organization is already running a learning cycle using their organization ID
618
654
  // const organizationId = skipConfigInfo.orgID;
619
655
  // const scheduler = LearningCycleScheduler.Instance;
620
656
  // const runningStatus = scheduler.isOrganizationRunningCycle(organizationId);
621
-
657
+
622
658
  // if (runningStatus.isRunning) {
623
659
  // LogStatus(`Learning cycle already in progress for organization ${organizationId}, started at ${runningStatus.startTime.toISOString()}`);
624
660
  // return {
@@ -659,7 +695,7 @@ export class AskSkipResolver {
659
695
 
660
696
  // // Register this organization as running a learning cycle
661
697
  // scheduler.registerRunningCycle(organizationId, learningCycleId);
662
-
698
+
663
699
  // try {
664
700
  // // Build the request to Skip learning API
665
701
  // LogStatus(`Building Skip Learning API request`);
@@ -699,29 +735,29 @@ export class AskSkipResolver {
699
735
  // if (!(await learningCycleEntity.Save())) {
700
736
  // LogError(`Failed to update learning cycle record: ${learningCycleEntity.LatestResult.Error}`);
701
737
  // }
702
-
738
+
703
739
  // return response;
704
740
  // }
705
- // }
741
+ // }
706
742
  // catch (error) {
707
743
  // // Make sure to update the learning cycle record as failed
708
744
  // learningCycleEntity.Status = 'Failed';
709
745
  // learningCycleEntity.EndedAt = new Date();
710
-
746
+
711
747
  // try {
712
748
  // await learningCycleEntity.Save();
713
- // }
749
+ // }
714
750
  // catch (saveError) {
715
751
  // LogError(`Failed to update learning cycle record after error: ${saveError}`);
716
752
  // }
717
-
753
+
718
754
  // // Re-throw the original error
719
755
  // throw error;
720
756
  // }
721
757
  // finally {
722
758
  // // Unregister the cycle/organizationId safely
723
759
  // try {
724
- // scheduler.unregisterRunningCycle(organizationId);
760
+ // scheduler.unregisterRunningCycle(organizationId);
725
761
  // }
726
762
  // catch (error) {
727
763
  // LogError(`Failed to unregister organization ${organizationId} from running cycles: ${error}`);
@@ -732,7 +768,7 @@ export class AskSkipResolver {
732
768
  // /**
733
769
  // * Handles the HTTP POST request to the Skip learning cycle API
734
770
  // * Sends the learning cycle request and processes the response
735
- // *
771
+ // *
736
772
  // * @param input The learning cycle request payload
737
773
  // * @param user User context for the request
738
774
  // * @param learningCycleId ID of the current learning cycle
@@ -740,8 +776,8 @@ export class AskSkipResolver {
740
776
  // * @returns Response from the Skip learning cycle API
741
777
  // */
742
778
  // protected async handleSimpleSkipLearningPostRequest(
743
- // input: SkipAPILearningCycleRequest,
744
- // user: UserInfo,
779
+ // input: SkipAPILearningCycleRequest,
780
+ // user: UserInfo,
745
781
  // learningCycleId: string,
746
782
  // agentID: string,
747
783
  // userPayload: UserPayload
@@ -773,7 +809,7 @@ export class AskSkipResolver {
773
809
  // // if (apiResponse.requestChanges && apiResponse.requestChanges.length > 0) {
774
810
  // // await this.processLearningCycleRequestChanges(apiResponse.requestChanges, user);
775
811
  // // }
776
-
812
+
777
813
  // return apiResponse;
778
814
  // } else {
779
815
  // return {
@@ -791,7 +827,7 @@ export class AskSkipResolver {
791
827
  /**
792
828
  * Handles the HTTP POST request to the Skip chat API
793
829
  * Sends the chat request and processes the response
794
- *
830
+ *
795
831
  * @param input The chat request payload
796
832
  * @param convoEntity The conversation entity object
797
833
  * @param convoDetailEntity The conversation detail entity object
@@ -817,9 +853,10 @@ export class AskSkipResolver {
817
853
  if (response && response.length > 0) {
818
854
  // the last object in the response array is the final response from the Skip API
819
855
  const apiResponse = <SkipAPIResponse>response[response.length - 1].value;
820
- const AIMessageConversationDetailID = createAIMessageConversationDetail && convoEntity
821
- ? await this.CreateAIMessageConversationDetail(apiResponse, convoEntity.ID, user, userPayload)
822
- : '';
856
+ const AIMessageConversationDetailID =
857
+ createAIMessageConversationDetail && convoEntity
858
+ ? await this.CreateAIMessageConversationDetail(apiResponse, convoEntity.ID, user, userPayload)
859
+ : '';
823
860
  // const apiResponse = <SkipAPIResponse>response.data;
824
861
  LogStatus(` Skip API response: ${apiResponse.responsePhase}`);
825
862
  return {
@@ -836,7 +873,7 @@ export class AskSkipResolver {
836
873
  if (convoEntity) {
837
874
  await this.setConversationStatus(convoEntity, 'Available', userPayload);
838
875
  }
839
-
876
+
840
877
  return {
841
878
  Success: false,
842
879
  Status: 'Error',
@@ -852,152 +889,157 @@ export class AskSkipResolver {
852
889
  if (convoEntity) {
853
890
  await this.setConversationStatus(convoEntity, 'Available', userPayload);
854
891
  }
855
-
892
+
856
893
  // Log the error for debugging
857
894
  LogError(`Error in handleSimpleSkipChatPostRequest: ${error}`);
858
-
895
+
859
896
  // Re-throw the error to propagate it up the stack
860
897
  throw error;
861
898
  }
862
899
  }
863
900
 
864
- // /**
865
- // * Processes note changes received from the Skip API learning cycle
866
- // * Applies changes to agent notes based on the learning cycle response
867
- // *
868
- // * @param noteChanges Changes to agent notes
869
- // * @param agentID ID of the Skip agent
870
- // * @param user User context for the request
871
- // * @returns Promise that resolves when processing is complete
872
- // */
873
- // protected async processLearningCycleNoteChanges(
874
- // noteChanges: SkipLearningCycleNoteChange[],
875
- // agentID: string,
876
- // user: UserInfo,
877
- // userPayload: UserPayload
878
- // ): Promise<void> {
879
- // const md = new Metadata();
880
-
881
- // // Filter out any operations on "Human" notes
882
- // const validNoteChanges = noteChanges.filter(change => {
883
- // // Check if the note is of type "Human"
884
- // if (change.note.agentNoteType === "Human") {
885
- // LogStatus(`WARNING: Ignoring ${change.changeType} operation on Human note with ID ${change.note.id}. Human notes cannot be modified by the
886
- // learning cycle.`);
887
- // return false;
888
- // }
889
- // return true;
890
- // });
891
-
892
- // // Process all valid note changes in parallel
893
- // await Promise.all(validNoteChanges.map(async (change) => {
894
- // try {
895
- // if (change.changeType === 'add' || change.changeType === 'update') {
896
- // await this.processAddOrUpdateSkipNote(change, agentID, user, userPayload);
897
- // } else if (change.changeType === 'delete') {
898
- // await this.processDeleteSkipNote(change, user, userPayload);
899
- // }
900
- // } catch (e) {
901
- // LogError(`Error processing note change: ${e}`);
902
- // }
903
- // }));
904
- // }
901
+ // /**
902
+ // * Processes note changes received from the Skip API learning cycle
903
+ // * Applies changes to agent notes based on the learning cycle response
904
+ // *
905
+ // * @param noteChanges Changes to agent notes
906
+ // * @param agentID ID of the Skip agent
907
+ // * @param user User context for the request
908
+ // * @returns Promise that resolves when processing is complete
909
+ // */
910
+ // protected async processLearningCycleNoteChanges(
911
+ // noteChanges: SkipLearningCycleNoteChange[],
912
+ // agentID: string,
913
+ // user: UserInfo,
914
+ // userPayload: UserPayload
915
+ // ): Promise<void> {
916
+ // const md = new Metadata();
917
+
918
+ // // Filter out any operations on "Human" notes
919
+ // const validNoteChanges = noteChanges.filter(change => {
920
+ // // Check if the note is of type "Human"
921
+ // if (change.note.agentNoteType === "Human") {
922
+ // LogStatus(`WARNING: Ignoring ${change.changeType} operation on Human note with ID ${change.note.id}. Human notes cannot be modified by the
923
+ // learning cycle.`);
924
+ // return false;
925
+ // }
926
+ // return true;
927
+ // });
905
928
 
906
- // /**
907
- // * Processes an add or update operation for a Skip agent note
908
- // * Creates a new note or updates an existing one based on the change type
909
- // *
910
- // * @param change The note change information
911
- // * @param agentID ID of the Skip agent
912
- // * @param user User context for the operation
913
- // * @returns Whether the operation was successful
914
- // */
915
- // protected async processAddOrUpdateSkipNote(change: SkipLearningCycleNoteChange, agentID: string, user: UserInfo, userPayload: UserPayload): Promise<boolean> {
916
- // try {
917
- // // Get the note entity object
918
- // const md = new Metadata();
919
- // const noteEntity = await md.GetEntityObject<AIAgentNoteEntity>('AI Agent Notes', user);
920
-
921
- // if (change.changeType === 'update') {
922
- // // Load existing note
923
- // const loadResult = await noteEntity.Load(change.note.id);
924
- // if (!loadResult) {
925
- // LogError(`Could not load note with ID ${change.note.id}`);
926
- // return false;
927
- // }
928
- // } else {
929
- // // Create a new note
930
- // noteEntity.NewRecord();
931
- // noteEntity.AgentID = agentID;
932
- // }
933
- // noteEntity.AgentNoteTypeID = this.getAgentNoteTypeIDByName('AI'); // always set to AI
934
- // noteEntity.Note = change.note.note;
935
- // noteEntity.Type = change.note.type;
929
+ // // Process all valid note changes in parallel
930
+ // await Promise.all(validNoteChanges.map(async (change) => {
931
+ // try {
932
+ // if (change.changeType === 'add' || change.changeType === 'update') {
933
+ // await this.processAddOrUpdateSkipNote(change, agentID, user, userPayload);
934
+ // } else if (change.changeType === 'delete') {
935
+ // await this.processDeleteSkipNote(change, user, userPayload);
936
+ // }
937
+ // } catch (e) {
938
+ // LogError(`Error processing note change: ${e}`);
939
+ // }
940
+ // }));
941
+ // }
936
942
 
937
- // if (change.note.type === 'User') {
938
- // noteEntity.UserID = change.note.userId;
939
- // }
943
+ // /**
944
+ // * Processes an add or update operation for a Skip agent note
945
+ // * Creates a new note or updates an existing one based on the change type
946
+ // *
947
+ // * @param change The note change information
948
+ // * @param agentID ID of the Skip agent
949
+ // * @param user User context for the operation
950
+ // * @returns Whether the operation was successful
951
+ // */
952
+ // protected async processAddOrUpdateSkipNote(change: SkipLearningCycleNoteChange, agentID: string, user: UserInfo, userPayload: UserPayload): Promise<boolean> {
953
+ // try {
954
+ // // Get the note entity object
955
+ // const md = new Metadata();
956
+ // const noteEntity = await md.GetEntityObject<AIAgentNoteEntity>('AI Agent Notes', user);
957
+
958
+ // if (change.changeType === 'update') {
959
+ // // Load existing note
960
+ // const loadResult = await noteEntity.Load(change.note.id);
961
+ // if (!loadResult) {
962
+ // LogError(`Could not load note with ID ${change.note.id}`);
963
+ // return false;
964
+ // }
965
+ // } else {
966
+ // // Create a new note
967
+ // noteEntity.NewRecord();
968
+ // noteEntity.AgentID = agentID;
969
+ // }
970
+ // noteEntity.AgentNoteTypeID = this.getAgentNoteTypeIDByName('AI'); // always set to AI
971
+ // noteEntity.Note = change.note.note;
972
+ // noteEntity.Type = change.note.type;
940
973
 
941
- // // Save the note
942
- // if (!(await noteEntity.Save())) {
943
- // LogError(`Error saving AI Agent Note: ${noteEntity.LatestResult.Error}`);
944
- // return false;
945
- // }
974
+ // if (change.note.type === 'User') {
975
+ // noteEntity.UserID = change.note.userId;
976
+ // }
946
977
 
947
- // return true;
948
- // } catch (e) {
949
- // LogError(`Error processing note change: ${e}`);
950
- // return false;
951
- // }
952
- // }
978
+ // // Save the note
979
+ // if (!(await noteEntity.Save())) {
980
+ // LogError(`Error saving AI Agent Note: ${noteEntity.LatestResult.Error}`);
981
+ // return false;
982
+ // }
953
983
 
954
- // /**
955
- // * Processes a delete operation for a Skip agent note
956
- // * Removes the specified note from the database
957
- // *
958
- // * @param change The note change information
959
- // * @param user User context for the operation
960
- // * @returns Whether the deletion was successful
961
- // */
962
- // protected async processDeleteSkipNote(change: SkipLearningCycleNoteChange, user: UserInfo, userPayload: UserPayload): Promise<boolean> {
963
- // // Get the note entity object
964
- // const md = new Metadata();
965
- // const noteEntity = await md.GetEntityObject<AIAgentNoteEntity>('AI Agent Notes', user);
966
-
967
- // // Load the note first
968
- // const loadResult = await noteEntity.Load(change.note.id);
969
-
970
- // if (!loadResult) {
971
- // LogError(`Could not load note with ID ${change.note.id} for deletion`);
972
- // return false;
973
- // }
974
-
975
- // // Double-check if the loaded note is of type "Human"
976
- // if (change.note.agentNoteType === "Human") {
977
- // LogStatus(`WARNING: Ignoring delete operation on Human note with ID ${change.note.id}. Human notes cannot be deleted by the learning
978
- // cycle.`);
979
- // return false;
980
- // }
981
-
982
- // // Proceed with deletion
983
- // if (!(await noteEntity.Delete())) {
984
- // LogError(`Error deleting AI Agent Note: ${noteEntity.LatestResult.Error}`);
985
- // return false;
986
- // }
987
-
988
- // return true;
989
- // }
984
+ // return true;
985
+ // } catch (e) {
986
+ // LogError(`Error processing note change: ${e}`);
987
+ // return false;
988
+ // }
989
+ // }
990
+
991
+ // /**
992
+ // * Processes a delete operation for a Skip agent note
993
+ // * Removes the specified note from the database
994
+ // *
995
+ // * @param change The note change information
996
+ // * @param user User context for the operation
997
+ // * @returns Whether the deletion was successful
998
+ // */
999
+ // protected async processDeleteSkipNote(change: SkipLearningCycleNoteChange, user: UserInfo, userPayload: UserPayload): Promise<boolean> {
1000
+ // // Get the note entity object
1001
+ // const md = new Metadata();
1002
+ // const noteEntity = await md.GetEntityObject<AIAgentNoteEntity>('AI Agent Notes', user);
1003
+
1004
+ // // Load the note first
1005
+ // const loadResult = await noteEntity.Load(change.note.id);
1006
+
1007
+ // if (!loadResult) {
1008
+ // LogError(`Could not load note with ID ${change.note.id} for deletion`);
1009
+ // return false;
1010
+ // }
1011
+
1012
+ // // Double-check if the loaded note is of type "Human"
1013
+ // if (change.note.agentNoteType === "Human") {
1014
+ // LogStatus(`WARNING: Ignoring delete operation on Human note with ID ${change.note.id}. Human notes cannot be deleted by the learning
1015
+ // cycle.`);
1016
+ // return false;
1017
+ // }
1018
+
1019
+ // // Proceed with deletion
1020
+ // if (!(await noteEntity.Delete())) {
1021
+ // LogError(`Error deleting AI Agent Note: ${noteEntity.LatestResult.Error}`);
1022
+ // return false;
1023
+ // }
1024
+
1025
+ // return true;
1026
+ // }
990
1027
 
991
1028
  /**
992
1029
  * Creates a conversation detail entry for an AI message
993
1030
  * Stores the AI response in the conversation history
994
- *
1031
+ *
995
1032
  * @param apiResponse The response from the Skip API
996
1033
  * @param conversationID ID of the conversation
997
1034
  * @param user User context for the operation
998
1035
  * @returns ID of the created conversation detail, or empty string if creation failed
999
1036
  */
1000
- protected async CreateAIMessageConversationDetail(apiResponse: SkipAPIResponse, conversationID: string, user: UserInfo, userPayload: UserPayload): Promise<string> {
1037
+ protected async CreateAIMessageConversationDetail(
1038
+ apiResponse: SkipAPIResponse,
1039
+ conversationID: string,
1040
+ user: UserInfo,
1041
+ userPayload: UserPayload
1042
+ ): Promise<string> {
1001
1043
  const md = new Metadata();
1002
1044
  const convoDetailEntityAI = <ConversationDetailEntity>await md.GetEntityObject('Conversation Details', user);
1003
1045
  convoDetailEntityAI.NewRecord();
@@ -1023,7 +1065,7 @@ export class AskSkipResolver {
1023
1065
  /**
1024
1066
  * Builds the base Skip API request with common fields and data
1025
1067
  * Creates the foundation for both chat and learning cycle requests
1026
- *
1068
+ *
1027
1069
  * @param contextUser The user making the request
1028
1070
  * @param dataSource The data source to use
1029
1071
  * @param includeEntities Whether to include entities in the request
@@ -1052,7 +1094,7 @@ export class AskSkipResolver {
1052
1094
  const queries = includeQueries ? this.BuildSkipQueries() : [];
1053
1095
  //const {notes, noteTypes} = includeNotes ? await this.BuildSkipAgentNotes(contextUser, filterUserNotesToContextUser) : {notes: [], noteTypes: []};
1054
1096
  const requests = includeRequests ? await this.BuildSkipRequests(contextUser) : [];
1055
-
1097
+
1056
1098
  // Setup access token if needed
1057
1099
  let accessToken: GetDataAccessToken;
1058
1100
  if (includeCallBackKeyAndAccessToken) {
@@ -1061,39 +1103,35 @@ export class AskSkipResolver {
1061
1103
  userEmail: contextUser.Email,
1062
1104
  userName: contextUser.Name,
1063
1105
  userID: contextUser.ID,
1064
- ...additionalTokenInfo
1106
+ ...additionalTokenInfo,
1065
1107
  };
1066
-
1067
- accessToken = registerAccessToken(
1068
- undefined,
1069
- 1000 * 60 * 10 /*10 minutes*/,
1070
- tokenInfo
1071
- );
1108
+
1109
+ accessToken = registerAccessToken(undefined, 1000 * 60 * 10 /*10 minutes*/, tokenInfo);
1072
1110
  }
1073
-
1111
+
1074
1112
  return {
1075
1113
  entities,
1076
1114
  queries,
1077
1115
  notes: undefined,
1078
1116
  noteTypes: undefined,
1079
1117
  userEmail: contextUser.Email,
1080
- requests,
1118
+ requests,
1081
1119
  accessToken,
1082
1120
  organizationID: skipConfigInfo.orgID,
1083
1121
  organizationInfo: configInfo?.askSkip?.organizationInfo,
1084
1122
  apiKeys: this.buildSkipAPIKeys(),
1085
1123
  // Favors public URL for conciseness or when behind a proxy for local development
1086
1124
  // otherwise uses base URL and GraphQL port/path from configuration
1087
- callingServerURL: accessToken ? (publicUrl || `${baseUrl}:${graphqlPort}${graphqlRootPath}`) : undefined,
1125
+ callingServerURL: accessToken ? publicUrl || `${baseUrl}:${graphqlPort}${graphqlRootPath}` : undefined,
1088
1126
  callingServerAPIKey: accessToken ? callbackAPIKey : undefined,
1089
- callingServerAccessToken: accessToken ? accessToken.Token : undefined
1127
+ callingServerAccessToken: accessToken ? accessToken.Token : undefined,
1090
1128
  };
1091
1129
  }
1092
1130
 
1093
1131
  /**
1094
1132
  * Builds the learning API request for Skip
1095
1133
  * Creates a request specific to the learning cycle operation
1096
- *
1134
+ *
1097
1135
  * @param learningCycleId ID of the current learning cycle
1098
1136
  * @param lastLearningCycleDate Date of the last completed learning cycle
1099
1137
  * @param includeEntities Whether to include entities in the request
@@ -1130,7 +1168,7 @@ export class AskSkipResolver {
1130
1168
  forceEntitiesRefresh,
1131
1169
  includeCallBackKeyAndAccessToken
1132
1170
  );
1133
-
1171
+
1134
1172
  // Get data specific to learning cycle
1135
1173
  const newConversations = await this.BuildSkipLearningCycleNewConversations(lastLearningCycleDate, dataSource, contextUser);
1136
1174
 
@@ -1146,7 +1184,7 @@ export class AskSkipResolver {
1146
1184
  notes: baseRequest.notes,
1147
1185
  noteTypes: baseRequest.noteTypes,
1148
1186
  requests: baseRequest.requests,
1149
- apiKeys: baseRequest.apiKeys
1187
+ apiKeys: baseRequest.apiKeys,
1150
1188
  };
1151
1189
 
1152
1190
  return input;
@@ -1155,7 +1193,7 @@ export class AskSkipResolver {
1155
1193
  /**
1156
1194
  * Loads the conversations that have been updated or added since the last learning cycle
1157
1195
  * These are used to train Skip and improve its understanding
1158
- *
1196
+ *
1159
1197
  * @param lastLearningCycleDate The date of the last learning cycle
1160
1198
  * @param dataSource Database connection
1161
1199
  * @param contextUser User context for the request
@@ -1170,31 +1208,35 @@ export class AskSkipResolver {
1170
1208
  const rv = new RunView();
1171
1209
 
1172
1210
  // Get all conversations with a conversation detail that has been updated (modified or added) since the last learning cycle
1173
- const conversationsSinceLastLearningCycle = await rv.RunView<ConversationEntity>({
1174
- EntityName: 'Conversations',
1175
- ExtraFilter: `ID IN (SELECT ConversationID FROM __mj.vwConversationDetails WHERE __mj_UpdatedAt >= '${lastLearningCycleDate.toISOString()}')`,
1176
- ResultType: 'entity_object',
1177
- }, contextUser);
1211
+ const conversationsSinceLastLearningCycle = await rv.RunView<ConversationEntity>(
1212
+ {
1213
+ EntityName: 'Conversations',
1214
+ ExtraFilter: `ID IN (SELECT ConversationID FROM __mj.vwConversationDetails WHERE __mj_UpdatedAt >= '${lastLearningCycleDate.toISOString()}')`,
1215
+ ResultType: 'entity_object',
1216
+ },
1217
+ contextUser
1218
+ );
1178
1219
 
1179
1220
  if (!conversationsSinceLastLearningCycle.Success || conversationsSinceLastLearningCycle.Results.length === 0) {
1180
1221
  return [];
1181
1222
  }
1182
1223
 
1183
- // Now we map the conversations to SkipConversations and return
1184
- return await Promise.all(conversationsSinceLastLearningCycle.Results.map(async (c) => {
1185
- return {
1186
- id: c.ID,
1187
- name: c.Name,
1188
- userId: c.UserID,
1189
- user: c.User,
1190
- description: c.Description,
1191
- messages: await this.LoadConversationDetailsIntoSkipMessages(dataSource, c.ID),
1192
- createdAt: c.__mj_CreatedAt,
1193
- updatedAt: c.__mj_UpdatedAt
1194
- };
1195
- }));
1196
- }
1197
- catch (e) {
1224
+ // Now we map the conversations to SkipConversations and return
1225
+ return await Promise.all(
1226
+ conversationsSinceLastLearningCycle.Results.map(async (c) => {
1227
+ return {
1228
+ id: c.ID,
1229
+ name: c.Name,
1230
+ userId: c.UserID,
1231
+ user: c.User,
1232
+ description: c.Description,
1233
+ messages: await this.LoadConversationDetailsIntoSkipMessages(dataSource, c.ID),
1234
+ createdAt: c.__mj_CreatedAt,
1235
+ updatedAt: c.__mj_UpdatedAt,
1236
+ };
1237
+ })
1238
+ );
1239
+ } catch (e) {
1198
1240
  LogError(`Error loading conversations since last learning cycle: ${e}`);
1199
1241
  return [];
1200
1242
  }
@@ -1203,13 +1245,11 @@ export class AskSkipResolver {
1203
1245
  /**
1204
1246
  * Builds an array of agent requests
1205
1247
  * These are requests that have been made to the AI agent
1206
- *
1248
+ *
1207
1249
  * @param contextUser User context for loading the requests
1208
1250
  * @returns Array of agent request objects
1209
1251
  */
1210
- protected async BuildSkipRequests(
1211
- contextUser: UserInfo
1212
- ): Promise<SkipAPIAgentRequest[]> {
1252
+ protected async BuildSkipRequests(contextUser: UserInfo): Promise<SkipAPIAgentRequest[]> {
1213
1253
  try {
1214
1254
  const md = new Metadata();
1215
1255
  const requestEntity = await md.GetEntityObject<AIAgentRequestEntity>('AI Agent Requests', contextUser);
@@ -1229,13 +1269,12 @@ export class AskSkipResolver {
1229
1269
  responseByUserId: r.ResponseByUserID,
1230
1270
  responseByUser: r.ResponseByUser,
1231
1271
  respondedAt: r.RespondedAt,
1232
- comments: r.Comments,
1272
+ comments: r.Comments,
1233
1273
  createdAt: r.__mj_CreatedAt,
1234
1274
  updatedAt: r.__mj_UpdatedAt,
1235
1275
  };
1236
1276
  });
1237
1277
  return requests;
1238
-
1239
1278
  } catch (e) {
1240
1279
  LogError(`Error loading requests: ${e}`);
1241
1280
  return [];
@@ -1245,7 +1284,7 @@ export class AskSkipResolver {
1245
1284
  /**
1246
1285
  * Gets the date of the last complete learning cycle for the Skip agent
1247
1286
  * Used to determine which data to include in the next learning cycle
1248
- *
1287
+ *
1249
1288
  * @param agentID ID of the Skip agent
1250
1289
  * @param user User context for the query
1251
1290
  * @returns Date of the last complete learning cycle, or epoch if none exists
@@ -1254,20 +1293,22 @@ export class AskSkipResolver {
1254
1293
  const md = new Metadata();
1255
1294
  const rv = new RunView();
1256
1295
 
1257
- const lastLearningCycleRV = await rv.RunView<AIAgentLearningCycleEntity>({
1258
- EntityName: 'AI Agent Learning Cycles',
1259
- ExtraFilter: `AgentID = '${agentID}' AND Status = 'Complete'`,
1260
- ResultType: 'entity_object',
1261
- OrderBy: 'StartedAt DESC',
1262
- MaxRows: 1,
1263
- }, user);
1296
+ const lastLearningCycleRV = await rv.RunView<AIAgentLearningCycleEntity>(
1297
+ {
1298
+ EntityName: 'AI Agent Learning Cycles',
1299
+ ExtraFilter: `AgentID = '${agentID}' AND Status = 'Complete'`,
1300
+ ResultType: 'entity_object',
1301
+ OrderBy: 'StartedAt DESC',
1302
+ MaxRows: 1,
1303
+ },
1304
+ user
1305
+ );
1264
1306
 
1265
1307
  const lastLearningCycle = lastLearningCycleRV.Results[0];
1266
1308
 
1267
1309
  if (lastLearningCycle) {
1268
1310
  return lastLearningCycle.StartedAt;
1269
- }
1270
- else {
1311
+ } else {
1271
1312
  // if no lerarning cycle found, return the epoch date
1272
1313
  return new Date(0);
1273
1314
  }
@@ -1276,7 +1317,7 @@ export class AskSkipResolver {
1276
1317
  /**
1277
1318
  * Builds the chat API request for Skip
1278
1319
  * Creates a request specific to a chat interaction
1279
- *
1320
+ *
1280
1321
  * @param messages Array of messages in the conversation
1281
1322
  * @param conversationId ID of the conversation
1282
1323
  * @param dataContext Data context associated with the conversation
@@ -1310,7 +1351,7 @@ export class AskSkipResolver {
1310
1351
  conversationId,
1311
1352
  requestPhase,
1312
1353
  };
1313
-
1354
+
1314
1355
  // Get base request data
1315
1356
  const baseRequest = await this.buildBaseSkipRequest(
1316
1357
  contextUser,
@@ -1334,41 +1375,48 @@ export class AskSkipResolver {
1334
1375
  conversationID: conversationId.toString(),
1335
1376
  dataContext: <DataContext>CopyScalarsAndArrays(dataContext), // we are casting this to DataContext as we're pushing this to the Skip API, and we don't want to send the real DataContext object, just a copy of the scalar and array properties
1336
1377
  requestPhase,
1337
- artifacts: artifacts
1378
+ artifacts: artifacts,
1338
1379
  };
1339
-
1380
+
1340
1381
  return input;
1341
1382
  }
1342
1383
 
1343
1384
  /**
1344
1385
  * Builds up an array of artifacts associated with a conversation
1345
1386
  * Artifacts are content or documents generated during conversations
1346
- *
1387
+ *
1347
1388
  * @param contextUser User context for the query
1348
1389
  * @param dataSource Database connection
1349
1390
  * @param conversationId ID of the conversation
1350
1391
  * @returns Array of artifacts associated with the conversation
1351
1392
  */
1352
- protected async buildSkipAPIArtifacts(contextUser: UserInfo, dataSource: mssql.ConnectionPool, conversationId: string): Promise<SkipAPIArtifact[]> {
1393
+ protected async buildSkipAPIArtifacts(
1394
+ contextUser: UserInfo,
1395
+ dataSource: mssql.ConnectionPool,
1396
+ conversationId: string
1397
+ ): Promise<SkipAPIArtifact[]> {
1353
1398
  const md = new Metadata();
1354
1399
  const ei = md.EntityByName('MJ: Conversation Artifacts');
1355
1400
  const rv = new RunView();
1356
- const results = await rv.RunViews([
1357
- {
1358
- EntityName: "MJ: Conversation Artifacts",
1359
- ExtraFilter: `ConversationID='${conversationId}'`, // get artifacts linked to this convo
1360
- OrderBy: "__mj_CreatedAt"
1361
- },
1362
- {
1363
- EntityName: "MJ: Artifact Types", // get all artifact types
1364
- OrderBy: "Name"
1365
- },
1366
- {
1367
- EntityName: "MJ: Conversation Artifact Versions",
1368
- ExtraFilter: `ConversationArtifactID IN (SELECT ID FROM [${ei.SchemaName}].[${ei.BaseView}] WHERE ConversationID='${conversationId}')`,
1369
- OrderBy: 'ConversationArtifactID, __mj_CreatedAt'
1370
- }
1371
- ], contextUser);
1401
+ const results = await rv.RunViews(
1402
+ [
1403
+ {
1404
+ EntityName: 'MJ: Conversation Artifacts',
1405
+ ExtraFilter: `ConversationID='${conversationId}'`, // get artifacts linked to this convo
1406
+ OrderBy: '__mj_CreatedAt',
1407
+ },
1408
+ {
1409
+ EntityName: 'MJ: Artifact Types', // get all artifact types
1410
+ OrderBy: 'Name',
1411
+ },
1412
+ {
1413
+ EntityName: 'MJ: Conversation Artifact Versions',
1414
+ ExtraFilter: `ConversationArtifactID IN (SELECT ID FROM [${ei.SchemaName}].[${ei.BaseView}] WHERE ConversationID='${conversationId}')`,
1415
+ OrderBy: 'ConversationArtifactID, __mj_CreatedAt',
1416
+ },
1417
+ ],
1418
+ contextUser
1419
+ );
1372
1420
  if (results && results.length > 0 && results.every((r) => r.Success)) {
1373
1421
  const types: SkipAPIArtifactType[] = results[1].Results.map((a: ArtifactTypeEntity) => {
1374
1422
  const retVal: SkipAPIArtifactType = {
@@ -1378,13 +1426,13 @@ export class AskSkipResolver {
1378
1426
  contentType: a.ContentType,
1379
1427
  enabled: a.IsEnabled,
1380
1428
  createdAt: a.__mj_CreatedAt,
1381
- updatedAt: a.__mj_UpdatedAt
1382
- }
1429
+ updatedAt: a.__mj_UpdatedAt,
1430
+ };
1383
1431
  return retVal;
1384
1432
  });
1385
1433
  const allConvoArtifacts = results[0].Results.map((a: ConversationArtifactEntity) => {
1386
1434
  const rawVersions: ConversationArtifactVersionEntity[] = results[2].Results as ConversationArtifactVersionEntity[];
1387
- const thisArtifactsVersions = rawVersions.filter(rv => rv.ConversationArtifactID === a.ID);
1435
+ const thisArtifactsVersions = rawVersions.filter((rv) => rv.ConversationArtifactID === a.ID);
1388
1436
  const versionsForThisArtifact: SkipAPIArtifactVersion[] = thisArtifactsVersions.map((v: ConversationArtifactVersionEntity) => {
1389
1437
  const versionRetVal: SkipAPIArtifactVersion = {
1390
1438
  id: v.ID,
@@ -1394,7 +1442,7 @@ export class AskSkipResolver {
1394
1442
  content: v.Content,
1395
1443
  comments: v.Comments,
1396
1444
  createdAt: v.__mj_CreatedAt,
1397
- updatedAt: v.__mj_UpdatedAt
1445
+ updatedAt: v.__mj_UpdatedAt,
1398
1446
  };
1399
1447
  return versionRetVal;
1400
1448
  });
@@ -1403,28 +1451,26 @@ export class AskSkipResolver {
1403
1451
  name: a.Name,
1404
1452
  description: a.Description,
1405
1453
  comments: a.Comments,
1406
- sharingScope: a.SharingScope as 'None' |'SpecificUsers' |'Everyone' |'Public',
1454
+ sharingScope: a.SharingScope as 'None' | 'SpecificUsers' | 'Everyone' | 'Public',
1407
1455
  versions: versionsForThisArtifact,
1408
1456
  conversationId: a.ConversationID,
1409
- artifactType: types.find((t => t.id === a.ArtifactTypeID)),
1457
+ artifactType: types.find((t) => t.id === a.ArtifactTypeID),
1410
1458
  createdAt: a.__mj_CreatedAt,
1411
- updatedAt: a.__mj_UpdatedAt
1459
+ updatedAt: a.__mj_UpdatedAt,
1412
1460
  };
1413
1461
  return artifactRetVal;
1414
1462
  });
1415
1463
 
1416
1464
  return allConvoArtifacts;
1417
- }
1418
- else {
1465
+ } else {
1419
1466
  return [];
1420
1467
  }
1421
1468
  }
1422
1469
 
1423
-
1424
1470
  /**
1425
1471
  * Executes a script in the context of a data context
1426
1472
  * Allows running code against data context objects
1427
- *
1473
+ *
1428
1474
  * @param dataSource Database connection
1429
1475
  * @param userPayload Information about the authenticated user
1430
1476
  * @param pubSub Publisher/subscriber for events
@@ -1443,7 +1489,22 @@ export class AskSkipResolver {
1443
1489
  if (!user) throw new Error(`User ${userPayload.email} not found in UserCache`);
1444
1490
  const dataContext: DataContext = new DataContext();
1445
1491
  await dataContext.Load(DataContextId, dataSource, true, false, 0, user);
1446
- const input = <SkipAPIRunScriptRequest>await this.buildSkipChatAPIRequest([], '', dataContext, 'run_existing_script', false, false, false, false, user, dataSource, false, false);
1492
+ const input = <SkipAPIRunScriptRequest>(
1493
+ await this.buildSkipChatAPIRequest(
1494
+ [],
1495
+ '',
1496
+ dataContext,
1497
+ 'run_existing_script',
1498
+ false,
1499
+ false,
1500
+ false,
1501
+ false,
1502
+ user,
1503
+ dataSource,
1504
+ false,
1505
+ false
1506
+ )
1507
+ );
1447
1508
  input.scriptText = ScriptText;
1448
1509
  return this.handleSimpleSkipChatPostRequest(input, undefined, undefined, undefined, userPayload.userRecord, userPayload);
1449
1510
  }
@@ -1451,7 +1512,7 @@ export class AskSkipResolver {
1451
1512
  /**
1452
1513
  * Builds the array of API keys for various AI services
1453
1514
  * These are used by Skip to call external AI services
1454
- *
1515
+ *
1455
1516
  * @returns Array of API keys for different vendor services
1456
1517
  */
1457
1518
  protected buildSkipAPIKeys(): SkipAPIRequestAPIKey[] {
@@ -1500,34 +1561,34 @@ export class AskSkipResolver {
1500
1561
  LogError(`User ${userPayload.email} not found in UserCache`);
1501
1562
  return null;
1502
1563
  }
1503
-
1564
+
1504
1565
  // Load the conversation
1505
1566
  const convoEntity = await md.GetEntityObject<ConversationEntity>('Conversations', user);
1506
1567
  const loadResult = await convoEntity.Load(ConversationId);
1507
-
1568
+
1508
1569
  if (!loadResult) {
1509
1570
  LogError(`Could not load conversation ${ConversationId} for re-attachment`);
1510
1571
  return null;
1511
1572
  }
1512
-
1573
+
1513
1574
  // Check if the conversation belongs to this user
1514
1575
  if (convoEntity.UserID !== user.ID) {
1515
1576
  LogError(`Conversation ${ConversationId} does not belong to user ${user.Email}`);
1516
1577
  return null;
1517
1578
  }
1518
-
1579
+
1519
1580
  // If the conversation is processing, reattach the session to receive updates
1520
1581
  if (convoEntity.Status === 'Processing') {
1521
1582
  // Add this session to the active streams for this conversation
1522
1583
  activeStreams.addSession(ConversationId, userPayload.sessionId);
1523
-
1584
+
1524
1585
  // Get the last known status message and start time from our cache
1525
1586
  const lastStatusMessage = activeStreams.getStatus(ConversationId) || 'Processing...';
1526
1587
  const startTime = activeStreams.getStartTime(ConversationId);
1527
-
1588
+
1528
1589
  // Check if the stream is still active
1529
1590
  const isStreamActive = activeStreams.isActive(ConversationId);
1530
-
1591
+
1531
1592
  if (isStreamActive) {
1532
1593
  // Send the last known status to the frontend
1533
1594
  const statusMessage = {
@@ -1537,20 +1598,22 @@ export class AskSkipResolver {
1537
1598
  conversationID: convoEntity.ID,
1538
1599
  message: lastStatusMessage,
1539
1600
  };
1540
-
1601
+
1541
1602
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
1542
1603
  pushStatusUpdates: {
1543
1604
  message: JSON.stringify(statusMessage),
1544
- sessionId: userPayload.sessionId
1545
- }
1605
+ sessionId: userPayload.sessionId,
1606
+ },
1546
1607
  });
1547
-
1548
- LogStatus(`Re-attached session ${userPayload.sessionId} to active stream for conversation ${ConversationId}, last status: ${lastStatusMessage}`);
1549
-
1608
+
1609
+ LogStatus(
1610
+ `Re-attached session ${userPayload.sessionId} to active stream for conversation ${ConversationId}, last status: ${lastStatusMessage}`
1611
+ );
1612
+
1550
1613
  // Return the status and start time
1551
1614
  return {
1552
1615
  lastStatusMessage,
1553
- startTime: startTime || convoEntity.__mj_UpdatedAt
1616
+ startTime: startTime || convoEntity.__mj_UpdatedAt,
1554
1617
  };
1555
1618
  } else {
1556
1619
  // Stream is inactive or doesn't exist, just send default status
@@ -1561,20 +1624,20 @@ export class AskSkipResolver {
1561
1624
  conversationID: convoEntity.ID,
1562
1625
  message: 'Processing...',
1563
1626
  };
1564
-
1627
+
1565
1628
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
1566
1629
  pushStatusUpdates: {
1567
1630
  message: JSON.stringify(statusMessage),
1568
- sessionId: userPayload.sessionId
1569
- }
1631
+ sessionId: userPayload.sessionId,
1632
+ },
1570
1633
  });
1571
-
1634
+
1572
1635
  LogStatus(`Re-attached session ${userPayload.sessionId} to conversation ${ConversationId}, but stream is inactive`);
1573
-
1636
+
1574
1637
  // Return default start time since stream is inactive
1575
1638
  return {
1576
1639
  lastStatusMessage: 'Processing...',
1577
- startTime: convoEntity.__mj_UpdatedAt
1640
+ startTime: convoEntity.__mj_UpdatedAt,
1578
1641
  };
1579
1642
  }
1580
1643
  } else {
@@ -1590,7 +1653,7 @@ export class AskSkipResolver {
1590
1653
  /**
1591
1654
  * Executes an analysis query with Skip
1592
1655
  * This is the primary entry point for general Skip conversations
1593
- *
1656
+ *
1594
1657
  * @param UserQuestion The question or message from the user
1595
1658
  * @param ConversationId ID of an existing conversation, or empty for a new conversation
1596
1659
  * @param dataSource Database connection
@@ -1637,8 +1700,21 @@ export class AskSkipResolver {
1637
1700
  AskSkipResolver._maxHistoricalMessages
1638
1701
  );
1639
1702
 
1640
- const conversationDetailCount = 1
1641
- const input = await this.buildSkipChatAPIRequest(messages, ConversationId, dataContext, 'initial_request', true, true, true, false, user, dataSource, ForceEntityRefresh === undefined ? false : ForceEntityRefresh, true);
1703
+ const conversationDetailCount = 1;
1704
+ const input = await this.buildSkipChatAPIRequest(
1705
+ messages,
1706
+ ConversationId,
1707
+ dataContext,
1708
+ 'initial_request',
1709
+ true,
1710
+ true,
1711
+ true,
1712
+ false,
1713
+ user,
1714
+ dataSource,
1715
+ ForceEntityRefresh === undefined ? false : ForceEntityRefresh,
1716
+ true
1717
+ );
1642
1718
 
1643
1719
  return this.HandleSkipChatRequest(
1644
1720
  input,
@@ -1658,28 +1734,27 @@ export class AskSkipResolver {
1658
1734
  );
1659
1735
  }
1660
1736
 
1661
-
1662
1737
  /**
1663
1738
  * Recursively builds the category path for a query
1664
- * @param md
1665
- * @param categoryID
1739
+ * @param md
1740
+ * @param categoryID
1666
1741
  */
1667
1742
  protected buildQueryCategoryPath(md: Metadata, categoryID: string): string {
1668
1743
  const cat = md.QueryCategories.find((c) => c.ID === categoryID);
1669
1744
  if (!cat) return '';
1670
1745
  if (!cat.ParentID) return cat.Name; // base case, no parent, just return the name
1671
1746
  const parentPath = this.buildQueryCategoryPath(md, cat.ParentID); // build the path recursively
1672
- return parentPath ? `${parentPath}/${cat.Name}` : cat.Name;
1747
+ return parentPath ? `${parentPath}/${cat.Name}` : cat.Name;
1673
1748
  }
1674
1749
 
1675
1750
  /**
1676
1751
  * Packages up queries from the metadata based on their status
1677
1752
  * Used to provide Skip with information about available queries
1678
- *
1753
+ *
1679
1754
  * @param status The status of queries to include
1680
1755
  * @returns Array of query information objects
1681
1756
  */
1682
- protected BuildSkipQueries(status: "Pending" | "In-Review" | "Approved" | "Rejected" | "Obsolete" = 'Approved'): SkipQueryInfo[] {
1757
+ protected BuildSkipQueries(status: 'Pending' | 'In-Review' | 'Approved' | 'Rejected' | 'Obsolete' = 'Approved'): SkipQueryInfo[] {
1683
1758
  const md = new Metadata();
1684
1759
  const approvedQueries = md.Queries.filter((q) => q.Status === status);
1685
1760
  return approvedQueries.map((q) => {
@@ -1743,33 +1818,33 @@ export class AskSkipResolver {
1743
1818
  createdAt: e.__mj_CreatedAt,
1744
1819
  updatedAt: e.__mj_UpdatedAt,
1745
1820
  };
1746
- })
1747
- }
1821
+ }),
1822
+ };
1748
1823
  });
1749
1824
  }
1750
1825
 
1751
1826
  // /**
1752
1827
  // * Builds up the array of notes and note types for Skip
1753
1828
  // * These notes are used to provide Skip with domain knowledge and context
1754
- // *
1829
+ // *
1755
1830
  // * @param contextUser User context for the request
1756
1831
  // * @returns Object containing arrays of notes and note types
1757
1832
  // */
1758
1833
  // protected async BuildSkipAgentNotes(contextUser: UserInfo, filterUserNotesToContextUser: boolean): Promise<{notes: SkipAPIAgentNote[], noteTypes: SkipAPIAgentNoteType[]}> {
1759
1834
  // try {
1760
1835
  // // if already configured this does nothing, just makes sure we're configured
1761
- // await AIEngine.Instance.Config(false, contextUser);
1836
+ // await AIEngine.Instance.Config(false, contextUser);
1762
1837
 
1763
1838
  // const agent: AIAgentEntityExtended = AIEngine.Instance.GetAgentByName('Skip');
1764
1839
  // if (agent) {
1765
1840
  // let notes: SkipAPIAgentNote[] = [];
1766
1841
  // let noteTypes: SkipAPIAgentNoteType[] = [];
1767
-
1842
+
1768
1843
  // notes = agent.Notes.map((r) => {
1769
1844
  // return {
1770
1845
  // id: r.ID,
1771
1846
  // agentNoteTypeId: r.AgentNoteTypeID,
1772
- // agentNoteType: r.AgentNoteType,
1847
+ // agentNoteType: r.AgentNoteType,
1773
1848
  // note: r.Note,
1774
1849
  // type: r.Type,
1775
1850
  // userId: r.UserID,
@@ -1781,7 +1856,7 @@ export class AskSkipResolver {
1781
1856
 
1782
1857
  // if (filterUserNotesToContextUser){
1783
1858
  // // filter out any notes that are not for this user
1784
- // notes = notes.filter((n) => n.type === 'Global' ||
1859
+ // notes = notes.filter((n) => n.type === 'Global' ||
1785
1860
  // (n.type === 'User' && n.userId === contextUser.ID));
1786
1861
  // }
1787
1862
 
@@ -1810,21 +1885,22 @@ export class AskSkipResolver {
1810
1885
  /**
1811
1886
  * Packs entity rows for inclusion in Skip requests
1812
1887
  * Provides sample data based on entity configuration
1813
- *
1888
+ *
1814
1889
  * @param e Entity information
1815
1890
  * @param dataSource Database connection
1816
1891
  * @returns Array of entity rows based on packing configuration
1817
1892
  */
1818
1893
  protected async PackEntityRows(e: EntityInfo, dataSource: mssql.ConnectionPool): Promise<any[]> {
1819
1894
  try {
1820
- if (e.RowsToPackWithSchema === 'None')
1821
- return [];
1895
+ if (e.RowsToPackWithSchema === 'None') return [];
1822
1896
 
1823
1897
  // only include columns that have a scopes including either All and/or AI or have Null for ScopeDefault
1824
1898
  const fields = e.Fields.filter((f) => {
1825
1899
  const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
1826
1900
  return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
1827
- }).map(f => `[${f.Name}]`).join(',');
1901
+ })
1902
+ .map((f) => `[${f.Name}]`)
1903
+ .join(',');
1828
1904
 
1829
1905
  // now run the query based on the row packing method
1830
1906
  let sql: string = '';
@@ -1857,12 +1933,10 @@ export class AskSkipResolver {
1857
1933
  const result = await request.query(sql);
1858
1934
  if (!result || !result.recordset) {
1859
1935
  return [];
1860
- }
1861
- else {
1936
+ } else {
1862
1937
  return result.recordset;
1863
1938
  }
1864
- }
1865
- catch (e) {
1939
+ } catch (e) {
1866
1940
  LogError(`AskSkipResolver::PackEntityRows: ${e}`);
1867
1941
  return [];
1868
1942
  }
@@ -1871,7 +1945,7 @@ export class AskSkipResolver {
1871
1945
  /**
1872
1946
  * Packs possible values for an entity field
1873
1947
  * These values help Skip understand the domain and valid values for fields
1874
- *
1948
+ *
1875
1949
  * @param f Field information
1876
1950
  * @param dataSource Database connection
1877
1951
  * @returns Array of possible values for the field
@@ -1880,39 +1954,39 @@ export class AskSkipResolver {
1880
1954
  try {
1881
1955
  if (f.ValuesToPackWithSchema === 'None') {
1882
1956
  return []; // don't pack anything
1883
- }
1884
- else if (f.ValuesToPackWithSchema === 'All') {
1957
+ } else if (f.ValuesToPackWithSchema === 'All') {
1885
1958
  // wants ALL of the distinct values
1886
1959
  return await this.GetFieldDistinctValues(f, dataSource);
1887
- }
1888
- else if (f.ValuesToPackWithSchema === 'Auto') {
1960
+ } else if (f.ValuesToPackWithSchema === 'Auto') {
1889
1961
  // default setting - pack based on the ValueListType
1890
1962
  if (f.ValueListTypeEnum === 'List') {
1891
1963
  // simple list of values in the Entity Field Values table
1892
1964
  return f.EntityFieldValues.map((v) => {
1893
- return {value: v.Value, displayValue: v.Value};
1965
+ return { value: v.Value, displayValue: v.Value };
1894
1966
  });
1895
- }
1896
- else if (f.ValueListTypeEnum === 'ListOrUserEntry') {
1897
- // could be a user provided value, OR the values in the list of possible values.
1967
+ } else if (f.ValueListTypeEnum === 'ListOrUserEntry') {
1968
+ // could be a user provided value, OR the values in the list of possible values.
1898
1969
  // get the distinct list of values from the DB and concat that with the f.EntityFieldValues array - deduped and return
1899
1970
  const values = await this.GetFieldDistinctValues(f, dataSource);
1900
1971
  if (!values || values.length === 0) {
1901
1972
  // no result, just return the EntityFieldValues
1902
1973
  return f.EntityFieldValues.map((v) => {
1903
- return {value: v.Value, displayValue: v.Value};
1974
+ return { value: v.Value, displayValue: v.Value };
1904
1975
  });
1905
- }
1906
- else {
1907
- return [...new Set([...f.EntityFieldValues.map((v) => {
1908
- return {value: v.Value, displayValue: v.Value};
1909
- }), ...values])];
1976
+ } else {
1977
+ return [
1978
+ ...new Set([
1979
+ ...f.EntityFieldValues.map((v) => {
1980
+ return { value: v.Value, displayValue: v.Value };
1981
+ }),
1982
+ ...values,
1983
+ ]),
1984
+ ];
1910
1985
  }
1911
1986
  }
1912
1987
  }
1913
1988
  return []; // if we get here, nothing to pack
1914
- }
1915
- catch (e) {
1989
+ } catch (e) {
1916
1990
  LogError(`AskSkipResolver::PackFieldPossibleValues: ${e}`);
1917
1991
  return [];
1918
1992
  }
@@ -1921,7 +1995,7 @@ export class AskSkipResolver {
1921
1995
  /**
1922
1996
  * Gets distinct values for a field from the database
1923
1997
  * Used to provide Skip with information about the possible values
1924
- *
1998
+ *
1925
1999
  * @param f Field information
1926
2000
  * @param dataSource Database connection
1927
2001
  * @returns Array of distinct values for the field
@@ -1933,64 +2007,70 @@ export class AskSkipResolver {
1933
2007
  const result = await request.query(sql);
1934
2008
  if (!result || !result.recordset) {
1935
2009
  return [];
1936
- }
1937
- else {
2010
+ } else {
1938
2011
  return result.recordset.map((r) => {
1939
2012
  return {
1940
- value: r[f.Name],
1941
- displayValue: r[f.Name]
1942
- };
2013
+ value: r[f.Name],
2014
+ displayValue: r[f.Name],
2015
+ };
1943
2016
  });
1944
2017
  }
1945
- }
1946
- catch (e) {
2018
+ } catch (e) {
1947
2019
  LogError(`AskSkipResolver::GetFieldDistinctValues: ${e}`);
1948
- return [];
2020
+ return [];
1949
2021
  }
1950
2022
  }
1951
2023
 
1952
-
1953
2024
  // SKIP ENTITIES CACHING
1954
2025
  // Static variables shared across all instances
1955
- private static __skipEntitiesCache$: BehaviorSubject<Promise<SkipEntityInfo[]> | null> = new BehaviorSubject<Promise<SkipEntityInfo[]> | null>(null);
2026
+ private static __skipEntitiesCache$: BehaviorSubject<Promise<SkipEntityInfo[]> | null> = new BehaviorSubject<Promise<
2027
+ SkipEntityInfo[]
2028
+ > | null>(null);
1956
2029
  private static __lastRefreshTime: number = 0;
1957
2030
 
1958
2031
  /**
1959
2032
  * Refreshes the Skip entities cache
1960
2033
  * Rebuilds the entity information that is provided to Skip
1961
- *
2034
+ *
1962
2035
  * @param dataSource Database connection
1963
2036
  * @returns Updated array of entity information
1964
2037
  */
1965
2038
  private async refreshSkipEntities(dataSource: mssql.ConnectionPool): Promise<SkipEntityInfo[]> {
1966
2039
  try {
1967
2040
  const md = new Metadata();
1968
- const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas ?? [])
1969
- .map((e) => e.trim().toLowerCase());
1970
-
2041
+ const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas ?? []).map((e) =>
2042
+ e.trim().toLowerCase()
2043
+ );
2044
+
1971
2045
  // get the list of entities
1972
2046
  const entities = md.Entities.filter((e) => {
1973
- if (!configInfo.askSkip.entitiesToSend.excludeSchemas.includes(e.SchemaName) ||
1974
- skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())) {
2047
+ if (
2048
+ !configInfo.askSkip.entitiesToSend.excludeSchemas.includes(e.SchemaName) ||
2049
+ skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())
2050
+ ) {
1975
2051
  const sd = e.ScopeDefault?.trim();
1976
2052
  if (sd && sd.length > 0) {
1977
2053
  const scopes = sd.split(',').map((s) => s.trim().toLowerCase()) ?? ['all'];
1978
- return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai') || skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase());
1979
- }
1980
- else {
2054
+ return (
2055
+ !scopes ||
2056
+ scopes.length === 0 ||
2057
+ scopes.includes('all') ||
2058
+ scopes.includes('ai') ||
2059
+ skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())
2060
+ );
2061
+ } else {
1981
2062
  return true; // no scope, so include it
1982
2063
  }
1983
2064
  }
1984
2065
  return false;
1985
2066
  });
1986
-
2067
+
1987
2068
  // now we have our list of entities, pack em up
1988
2069
  const result = await Promise.all(entities.map((e) => this.PackSingleSkipEntityInfo(e, dataSource)));
1989
-
2070
+
1990
2071
  AskSkipResolver.__lastRefreshTime = Date.now(); // Update last refresh time
1991
- return result;
1992
- }
1993
- catch (e) {
2072
+ return result;
2073
+ } catch (e) {
1994
2074
  LogError(`AskSkipResolver::refreshSkipEntities: ${e}`);
1995
2075
  return [];
1996
2076
  }
@@ -1999,27 +2079,30 @@ export class AskSkipResolver {
1999
2079
  /**
2000
2080
  * Builds or retrieves Skip entities from cache
2001
2081
  * Uses caching to avoid expensive rebuilding of entity information
2002
- *
2082
+ *
2003
2083
  * @param dataSource Database connection
2004
2084
  * @param forceRefresh Whether to force a refresh regardless of cache state
2005
2085
  * @param refreshIntervalMinutes Minutes before cache expires
2006
2086
  * @returns Array of entity information
2007
2087
  */
2008
- public async BuildSkipEntities(dataSource: mssql.ConnectionPool, forceRefresh: boolean = false, refreshIntervalMinutes: number = 15): Promise<SkipEntityInfo[]> {
2088
+ public async BuildSkipEntities(
2089
+ dataSource: mssql.ConnectionPool,
2090
+ forceRefresh: boolean = false,
2091
+ refreshIntervalMinutes: number = 15
2092
+ ): Promise<SkipEntityInfo[]> {
2009
2093
  try {
2010
2094
  const now = Date.now();
2011
- const cacheExpired = (now - AskSkipResolver.__lastRefreshTime) > (refreshIntervalMinutes * 60 * 1000);
2012
-
2095
+ const cacheExpired = now - AskSkipResolver.__lastRefreshTime > refreshIntervalMinutes * 60 * 1000;
2096
+
2013
2097
  // If force refresh is requested OR cache expired OR cache is empty, refresh
2014
2098
  if (forceRefresh || cacheExpired || AskSkipResolver.__skipEntitiesCache$.value === null) {
2015
2099
  console.log(`Forcing Skip Entities refresh: ${forceRefresh}, Cache Expired: ${cacheExpired}`);
2016
2100
  const newData = this.refreshSkipEntities(dataSource);
2017
2101
  AskSkipResolver.__skipEntitiesCache$.next(newData);
2018
2102
  }
2019
-
2020
- return AskSkipResolver.__skipEntitiesCache$.pipe(take(1)).toPromise();
2021
- }
2022
- catch (e) {
2103
+
2104
+ return AskSkipResolver.__skipEntitiesCache$.pipe(take(1)).toPromise();
2105
+ } catch (e) {
2023
2106
  LogError(`AskSkipResolver::BuildSkipEntities: ${e}`);
2024
2107
  return [];
2025
2108
  }
@@ -2028,7 +2111,7 @@ export class AskSkipResolver {
2028
2111
  /**
2029
2112
  * Packs information about a single entity for Skip
2030
2113
  * Includes fields, relationships, and sample data
2031
- *
2114
+ *
2032
2115
  * @param e Entity information
2033
2116
  * @param dataSource Database connection
2034
2117
  * @returns Packaged entity information
@@ -2041,26 +2124,27 @@ export class AskSkipResolver {
2041
2124
  schemaName: e.SchemaName,
2042
2125
  baseView: e.BaseView,
2043
2126
  description: e.Description,
2044
-
2045
- fields: await Promise.all(e.Fields.filter(f => {
2046
- // we want to check the scopes for the field level and make sure it is either All or AI or has both
2047
- const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
2048
- return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
2049
- }).map(f => {
2050
- return this.PackSingleSkipEntityField(f, dataSource);
2051
- })),
2052
-
2127
+
2128
+ fields: await Promise.all(
2129
+ e.Fields.filter((f) => {
2130
+ // we want to check the scopes for the field level and make sure it is either All or AI or has both
2131
+ const scopes = f.ScopeDefault?.split(',').map((s) => s.trim().toLowerCase());
2132
+ return !scopes || scopes.length === 0 || scopes.includes('all') || scopes.includes('ai');
2133
+ }).map((f) => {
2134
+ return this.PackSingleSkipEntityField(f, dataSource);
2135
+ })
2136
+ ),
2137
+
2053
2138
  relatedEntities: e.RelatedEntities.map((r) => {
2054
2139
  return this.PackSingleSkipEntityRelationship(r);
2055
2140
  }),
2056
-
2141
+
2057
2142
  rowsPacked: e.RowsToPackWithSchema,
2058
2143
  rowsSampleMethod: e.RowsToPackSampleMethod,
2059
- rows: await this.PackEntityRows(e, dataSource)
2144
+ rows: await this.PackEntityRows(e, dataSource),
2060
2145
  };
2061
2146
  return ret;
2062
- }
2063
- catch (e) {
2147
+ } catch (e) {
2064
2148
  LogError(`AskSkipResolver::PackSingleSkipEntityInfo: ${e}`);
2065
2149
  return null;
2066
2150
  }
@@ -2069,7 +2153,7 @@ export class AskSkipResolver {
2069
2153
  /**
2070
2154
  * Packs information about a single entity relationship
2071
2155
  * These relationships help Skip understand the data model
2072
- *
2156
+ *
2073
2157
  * @param r Relationship information
2074
2158
  * @returns Packaged relationship information
2075
2159
  */
@@ -2089,8 +2173,7 @@ export class AskSkipResolver {
2089
2173
  relatedEntity: r.RelatedEntity,
2090
2174
  relatedEntityBaseView: r.RelatedEntityBaseView,
2091
2175
  };
2092
- }
2093
- catch (e) {
2176
+ } catch (e) {
2094
2177
  LogError(`AskSkipResolver::PackSingleSkipEntityRelationship: ${e}`);
2095
2178
  return null;
2096
2179
  }
@@ -2099,7 +2182,7 @@ export class AskSkipResolver {
2099
2182
  /**
2100
2183
  * Packs information about a single entity field
2101
2184
  * Includes metadata and possible values
2102
- *
2185
+ *
2103
2186
  * @param f Field information
2104
2187
  * @param dataSource Database connection
2105
2188
  * @returns Packaged field information
@@ -2137,8 +2220,7 @@ export class AskSkipResolver {
2137
2220
  relatedEntityBaseView: f.RelatedEntityBaseView,
2138
2221
  possibleValues: await this.PackFieldPossibleValues(f, dataSource),
2139
2222
  };
2140
- }
2141
- catch (e) {
2223
+ } catch (e) {
2142
2224
  LogError(`AskSkipResolver::PackSingleSkipEntityField: ${e}`);
2143
2225
  return null;
2144
2226
  }
@@ -2147,7 +2229,7 @@ export class AskSkipResolver {
2147
2229
  /**
2148
2230
  * Handles initial object loading for Skip chat interactions
2149
2231
  * Creates or loads conversation objects, data contexts, and other required entities
2150
- *
2232
+ *
2151
2233
  * @param dataSource Database connection
2152
2234
  * @param ConversationId ID of an existing conversation, or empty for a new one
2153
2235
  * @param UserQuestion The user's question or message
@@ -2192,8 +2274,7 @@ export class AskSkipResolver {
2192
2274
  LogError(`Creating a new data context failed`, undefined, dataContextEntity.LatestResult);
2193
2275
  throw new Error(`Creating a new data context failed`);
2194
2276
  }
2195
- }
2196
- else {
2277
+ } else {
2197
2278
  const dcLoadResult = await dataContextEntity.Load(DataContextId);
2198
2279
  if (!dcLoadResult) {
2199
2280
  throw new Error(`Loading DataContextEntity for DataContextId ${DataContextId} failed`);
@@ -2210,17 +2291,14 @@ export class AskSkipResolver {
2210
2291
  LogError(`Error saving DataContextEntity for conversation: ${ConversationId}`, undefined, dataContextEntity.LatestResult);
2211
2292
  }
2212
2293
  }
2213
- }
2214
- else {
2294
+ } else {
2215
2295
  LogError(`Creating a new conversation failed`, undefined, convoEntity.LatestResult);
2216
2296
  throw new Error(`Creating a new conversation failed`);
2217
2297
  }
2218
- }
2219
- else {
2298
+ } else {
2220
2299
  throw new Error(`User ${userPayload.email} not found in UserCache`);
2221
2300
  }
2222
- }
2223
- else {
2301
+ } else {
2224
2302
  await convoEntity.Load(ConversationId); // load the existing conversation, will need it later
2225
2303
  dataContextEntity = await md.GetEntityObject<DataContextEntity>('Data Contexts', user);
2226
2304
 
@@ -2233,19 +2311,16 @@ export class AskSkipResolver {
2233
2311
  if (!convoEntitySaveResult) {
2234
2312
  LogError(`Error saving conversation entity for conversation: ${ConversationId}`, undefined, convoEntity.LatestResult);
2235
2313
  }
2236
- }
2237
- else {
2238
- // note - we ignore the parameter DataContextId if it is passed in, we will use the data context from the conversation that is saved.
2314
+ } else {
2315
+ // note - we ignore the parameter DataContextId if it is passed in, we will use the data context from the conversation that is saved.
2239
2316
  // If a user wants to change the data context for a convo, they can do that elsewhere
2240
2317
  console.warn(
2241
2318
  `AskSkipResolver: DataContextId ${DataContextId} was passed in but it was ignored because it was different than the DataContextID in the conversation ${convoEntity.DataContextID}`
2242
2319
  );
2243
2320
  }
2244
2321
  // only load if we have a data context here, otherwise we have a new record in the dataContext entity
2245
- if (convoEntity.DataContextID)
2246
- await dataContextEntity.Load(convoEntity.DataContextID);
2247
- }
2248
- else if ((!DataContextId || DataContextId.length === 0) && (!convoEntity.DataContextID || convoEntity.DataContextID.length === 0)) {
2322
+ if (convoEntity.DataContextID) await dataContextEntity.Load(convoEntity.DataContextID);
2323
+ } else if ((!DataContextId || DataContextId.length === 0) && (!convoEntity.DataContextID || convoEntity.DataContextID.length === 0)) {
2249
2324
  // in this branch of the logic we don't have a passed in DataContextId and we don't have a DataContextID in the conversation, so we need to save the data context, get the ID,
2250
2325
  // update the conversation and save it as well
2251
2326
  dataContextEntity.NewRecord();
@@ -2254,14 +2329,11 @@ export class AskSkipResolver {
2254
2329
  if (await dataContextEntity.Save()) {
2255
2330
  DataContextId = convoEntity.DataContextID;
2256
2331
  convoEntity.DataContextID = dataContextEntity.ID;
2257
- if (!await convoEntity.Save()) {
2332
+ if (!(await convoEntity.Save())) {
2258
2333
  LogError(`Error saving conversation entity for conversation: ${ConversationId}`, undefined, convoEntity.LatestResult);
2259
2334
  }
2260
- }
2261
- else
2262
- LogError(`Error saving DataContextEntity for conversation: ${ConversationId}`, undefined, dataContextEntity.LatestResult);
2263
- }
2264
- else {
2335
+ } else LogError(`Error saving DataContextEntity for conversation: ${ConversationId}`, undefined, dataContextEntity.LatestResult);
2336
+ } else {
2265
2337
  // finally in this branch we get here if we have a DataContextId passed in and it is the same as the DataContextID in the conversation, in this case simply load the data context
2266
2338
  await dataContextEntity.Load(convoEntity.DataContextID);
2267
2339
  }
@@ -2281,7 +2353,7 @@ export class AskSkipResolver {
2281
2353
  LogError(`Error saving conversation detail entity for user message: ${UserQuestion}`, undefined, convoDetailEntity.LatestResult);
2282
2354
  }
2283
2355
 
2284
- const dataContext = MJGlobal.Instance.ClassFactory.CreateInstance<DataContext>(DataContext);
2356
+ const dataContext = MJGlobal.Instance.ClassFactory.CreateInstance<DataContext>(DataContext);
2285
2357
  await dataContext.Load(dataContextEntity.ID, dataSource, false, false, 0, user);
2286
2358
  return { dataContext, convoEntity, dataContextEntity, convoDetailEntity };
2287
2359
  }
@@ -2289,7 +2361,7 @@ export class AskSkipResolver {
2289
2361
  /**
2290
2362
  * Loads conversation details from the database and transforms them into Skip message format
2291
2363
  * Used to provide Skip with conversation history for context
2292
- *
2364
+ *
2293
2365
  * @param dataSource Database connection
2294
2366
  * @param ConversationId ID of the conversation to load details for
2295
2367
  * @param maxHistoricalMessages Maximum number of historical messages to include
@@ -2309,10 +2381,10 @@ export class AskSkipResolver {
2309
2381
  // load up all the conversation details from the database server
2310
2382
  const md = new Metadata();
2311
2383
  const e = md.Entities.find((e) => e.Name === 'Conversation Details');
2312
-
2384
+
2313
2385
  // Add role filter if specified
2314
2386
  const roleFilterClause = roleFilter ? ` AND Role = '${roleFilter}'` : '';
2315
-
2387
+
2316
2388
  const sql = `SELECT
2317
2389
  ${maxHistoricalMessages ? 'TOP ' + maxHistoricalMessages : ''} *
2318
2390
  FROM
@@ -2323,8 +2395,7 @@ export class AskSkipResolver {
2323
2395
  BY __mj_CreatedAt DESC`;
2324
2396
  const request = new mssql.Request(dataSource);
2325
2397
  const result = await request.query(sql);
2326
- if (!result || !result.recordset)
2327
- throw new Error(`Error running SQL: ${sql}`);
2398
+ if (!result || !result.recordset) throw new Error(`Error running SQL: ${sql}`);
2328
2399
  else {
2329
2400
  // first, let's sort the result array into a local variable called returnData and in that we will sort by __mj_CreatedAt in ASCENDING order so we have the right chronological order
2330
2401
  // the reason we're doing a LOCAL sort here is because in the SQL query above, we're sorting in DESCENDING order so we can use the TOP clause to limit the number of records and get the
@@ -2359,7 +2430,7 @@ export class AskSkipResolver {
2359
2430
  executionResults: analysisDetail.executionResults,
2360
2431
  tableDataColumns: analysisDetail.tableDataColumns,
2361
2432
  componentOptions: analysisDetail.componentOptions,
2362
- artifactRequest: analysisDetail.artifactRequest
2433
+ artifactRequest: analysisDetail.artifactRequest,
2363
2434
  });
2364
2435
  } else if (detail?.responsePhase === SkipResponsePhase.ClarifyingQuestion) {
2365
2436
  const clarifyingQuestionDetail = <SkipAPIClarifyingQuestionResponse>detail;
@@ -2397,7 +2468,7 @@ export class AskSkipResolver {
2397
2468
  /**
2398
2469
  * Maps database role values to Skip API role format
2399
2470
  * Converts role names from database format to the format expected by Skip API
2400
- *
2471
+ *
2401
2472
  * @param role Database role value
2402
2473
  * @returns Skip API role value ('user' or 'system')
2403
2474
  */
@@ -2415,7 +2486,7 @@ export class AskSkipResolver {
2415
2486
  /**
2416
2487
  * Handles the main Skip chat request processing flow
2417
2488
  * Routes the request through the different phases based on the Skip API response
2418
- *
2489
+ *
2419
2490
  * @param input Skip API request to send
2420
2491
  * @param UserQuestion The question or message from the user
2421
2492
  * @param user User information
@@ -2443,7 +2514,7 @@ export class AskSkipResolver {
2443
2514
  convoEntity: ConversationEntity,
2444
2515
  convoDetailEntity: ConversationDetailEntity,
2445
2516
  dataContext: DataContext,
2446
- dataContextEntity: DataContextEntity,
2517
+ dataContextEntity: DataContextEntity,
2447
2518
  conversationDetailCount: number,
2448
2519
  startTime: Date
2449
2520
  ): Promise<AskSkipResultType> {
@@ -2499,10 +2570,10 @@ export class AskSkipResolver {
2499
2570
  LogStatus(JSON.stringify(message, null, 4));
2500
2571
  if (message.type === 'status_update') {
2501
2572
  const statusContent = message.value.messages[0].content;
2502
-
2573
+
2503
2574
  // Store the status in our active streams cache
2504
2575
  activeStreams.updateStatus(ConversationId, statusContent, userPayload.sessionId);
2505
-
2576
+
2506
2577
  // Publish to all sessions listening to this conversation
2507
2578
  const sessionIds = activeStreams.getSessionIds(ConversationId);
2508
2579
  for (const sessionId of sessionIds) {
@@ -2523,10 +2594,10 @@ export class AskSkipResolver {
2523
2594
  } catch (error) {
2524
2595
  // Set conversation status to Available on error so user can try again
2525
2596
  await this.setConversationStatus(convoEntity, 'Available', userPayload, pubSub);
2526
-
2597
+
2527
2598
  // Log the error for debugging
2528
2599
  LogError(`Error in HandleSkipChatRequest sendPostRequest: ${error}`);
2529
-
2600
+
2530
2601
  // Publish error status update to user
2531
2602
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
2532
2603
  message: JSON.stringify({
@@ -2537,7 +2608,7 @@ export class AskSkipResolver {
2537
2608
  }),
2538
2609
  sessionId: userPayload.sessionId,
2539
2610
  });
2540
-
2611
+
2541
2612
  // Re-throw the error to propagate it up the stack
2542
2613
  throw error;
2543
2614
  }
@@ -2564,7 +2635,7 @@ export class AskSkipResolver {
2564
2635
  convoEntity,
2565
2636
  convoDetailEntity,
2566
2637
  dataContext,
2567
- dataContextEntity,
2638
+ dataContextEntity,
2568
2639
  conversationDetailCount,
2569
2640
  startTime
2570
2641
  );
@@ -2581,7 +2652,7 @@ export class AskSkipResolver {
2581
2652
  pubSub,
2582
2653
  convoEntity,
2583
2654
  convoDetailEntity,
2584
- startTime,
2655
+ startTime
2585
2656
  );
2586
2657
  } else if (apiResponse.responsePhase === 'analysis_complete') {
2587
2658
  return await this.HandleAnalysisComplete(
@@ -2638,7 +2709,7 @@ export class AskSkipResolver {
2638
2709
  /**
2639
2710
  * Publishes a status update message to the user based on the Skip API response
2640
2711
  * Provides feedback about what phase of processing is happening
2641
- *
2712
+ *
2642
2713
  * @param apiResponse The response from the Skip API
2643
2714
  * @param userPayload User payload from context
2644
2715
  * @param conversationID ID of the conversation
@@ -2679,7 +2750,7 @@ export class AskSkipResolver {
2679
2750
  /**
2680
2751
  * Handles the analysis complete phase of the Skip chat process
2681
2752
  * Finalizes the conversation and creates necessary artifacts
2682
- *
2753
+ *
2683
2754
  * @param apiRequest The original request sent to Skip
2684
2755
  * @param apiResponse The analysis complete response from Skip
2685
2756
  * @param UserQuestion The original user question
@@ -2745,7 +2816,7 @@ export class AskSkipResolver {
2745
2816
  /**
2746
2817
  * Handles the clarifying question phase of the Skip chat process
2747
2818
  * Creates a conversation detail for the clarifying question from Skip
2748
- *
2819
+ *
2749
2820
  * @param apiRequest The original request sent to Skip
2750
2821
  * @param apiResponse The clarifying question response from Skip
2751
2822
  * @param UserQuestion The original user question
@@ -2781,10 +2852,10 @@ export class AskSkipResolver {
2781
2852
  convoDetailEntityAI.Role = 'AI';
2782
2853
  convoDetailEntityAI.HiddenToUser = false;
2783
2854
  convoDetailEntityAI.CompletionTime = endTime.getTime() - startTime.getTime();
2784
-
2855
+
2785
2856
  // Set conversation status back to Available since we need user input for the clarifying question
2786
2857
  await this.setConversationStatus(convoEntity, 'Available', userPayload, pubSub);
2787
-
2858
+
2788
2859
  if (await convoDetailEntityAI.Save()) {
2789
2860
  return {
2790
2861
  Success: true,
@@ -2816,7 +2887,7 @@ export class AskSkipResolver {
2816
2887
  /**
2817
2888
  * Handles the data request phase of the Skip chat process
2818
2889
  * Processes data requests from Skip and loads requested data
2819
- *
2890
+ *
2820
2891
  * @param apiRequest The original request sent to Skip
2821
2892
  * @param apiResponse The data request response from Skip
2822
2893
  * @param UserQuestion The original user question
@@ -2844,7 +2915,7 @@ export class AskSkipResolver {
2844
2915
  convoEntity: ConversationEntity,
2845
2916
  convoDetailEntity: ConversationDetailEntity,
2846
2917
  dataContext: DataContext,
2847
- dataContextEntity: DataContextEntity,
2918
+ dataContextEntity: DataContextEntity,
2848
2919
  conversationDetailCount: number,
2849
2920
  startTime: Date
2850
2921
  ): Promise<AskSkipResultType> {
@@ -2975,7 +3046,7 @@ export class AskSkipResolver {
2975
3046
  convoEntity,
2976
3047
  convoDetailEntity,
2977
3048
  dataContext,
2978
- dataContextEntity,
3049
+ dataContextEntity,
2979
3050
  conversationDetailCount,
2980
3051
  startTime
2981
3052
  );
@@ -2988,7 +3059,7 @@ export class AskSkipResolver {
2988
3059
  /**
2989
3060
  * Finishes a successful conversation and notifies the user
2990
3061
  * Creates necessary records, artifacts, and notifications
2991
- *
3062
+ *
2992
3063
  * @param apiResponse The analysis complete response from Skip
2993
3064
  * @param dataContext Data context associated with the conversation
2994
3065
  * @param dataContextEntity Data context entity
@@ -3032,40 +3103,39 @@ export class AskSkipResolver {
3032
3103
  artifactEntity.Name = apiResponse.artifactRequest.name;
3033
3104
  artifactEntity.Description = apiResponse.artifactRequest.description;
3034
3105
  // make sure AI Engine is configured.
3035
- await AIEngine.Instance.Config(false, user)
3106
+ await AIEngine.Instance.Config(false, user);
3036
3107
  artifactEntity.ArtifactTypeID = AIEngine.Instance.ArtifactTypes.find((t) => t.Name === 'Report')?.ID;
3037
3108
  artifactEntity.SharingScope = 'None';
3038
3109
 
3039
3110
  if (await artifactEntity.Save()) {
3040
3111
  // saved, grab the new ID
3041
3112
  artifactId = artifactEntity.ID;
3042
- }
3043
- else {
3113
+ } else {
3044
3114
  LogError(`Error saving artifact entity for conversation: ${convoEntity.ID}`, undefined, artifactEntity.LatestResult);
3045
3115
  }
3046
3116
  newVersion = 1;
3047
- }
3048
- else {
3117
+ } else {
3049
3118
  // we are updating an existing artifact with a new vesrion so we need to get the old max version and increment it
3050
- const ei = md.EntityByName("MJ: Conversation Artifact Versions");
3119
+ const ei = md.EntityByName('MJ: Conversation Artifact Versions');
3051
3120
  const sSQL = `SELECT ISNULL(MAX(Version),0) AS MaxVersion FROM [${ei.SchemaName}].[${ei.BaseView}] WHERE ConversationArtifactID = '${artifactId}'`;
3052
3121
  try {
3053
3122
  const request = new mssql.Request(dataSource);
3054
3123
  const result = await request.query(sSQL);
3055
3124
  if (result && result.recordset && result.recordset.length > 0) {
3056
3125
  newVersion = result.recordset[0].MaxVersion + 1;
3057
- }
3058
- else {
3126
+ } else {
3059
3127
  LogError(`Error getting max version for artifact ID: ${artifactId}`, undefined, result);
3060
3128
  }
3061
- }
3062
- catch (e) {
3129
+ } catch (e) {
3063
3130
  LogError(`Error getting max version for artifact ID: ${artifactId}`, undefined, e);
3064
3131
  }
3065
3132
  }
3066
3133
  if (artifactId && newVersion > 0) {
3067
3134
  // only do this if we were provided an artifact ID or we saved a new one above successfully
3068
- const artifactVersionEntity = await md.GetEntityObject<ConversationArtifactVersionEntity>('MJ: Conversation Artifact Versions', user);
3135
+ const artifactVersionEntity = await md.GetEntityObject<ConversationArtifactVersionEntity>(
3136
+ 'MJ: Conversation Artifact Versions',
3137
+ user
3138
+ );
3069
3139
  // create the new artifact version here
3070
3140
  artifactVersionEntity.NewRecord();
3071
3141
  artifactVersionEntity.ConversationArtifactID = artifactId;
@@ -3075,9 +3145,8 @@ export class AskSkipResolver {
3075
3145
  if (await artifactVersionEntity.Save()) {
3076
3146
  // success saving the new version, set the artifactVersionId
3077
3147
  artifactVersionId = artifactVersionEntity.ID;
3078
- }
3079
- else {
3080
- LogError(`Error saving Artifact Version record`)
3148
+ } else {
3149
+ LogError(`Error saving Artifact Version record`);
3081
3150
  }
3082
3151
  }
3083
3152
  }
@@ -3091,14 +3160,14 @@ export class AskSkipResolver {
3091
3160
  convoDetailEntityAI.Role = 'AI';
3092
3161
  convoDetailEntityAI.HiddenToUser = false;
3093
3162
  convoDetailEntityAI.CompletionTime = endTime.getTime() - startTime.getTime();
3094
-
3163
+
3095
3164
  if (artifactId && artifactId.length > 0) {
3096
3165
  // bind the new convo detail record to the artifact + version for this response
3097
3166
  convoDetailEntityAI.ArtifactID = artifactId;
3098
3167
  if (artifactVersionId && artifactVersionId.length > 0) {
3099
3168
  convoDetailEntityAI.ArtifactVersionID = artifactVersionId;
3100
3169
  }
3101
- }
3170
+ }
3102
3171
 
3103
3172
  const convoDetailSaveResult: boolean = await convoDetailEntityAI.Save();
3104
3173
  if (!convoDetailSaveResult) {
@@ -3107,19 +3176,19 @@ export class AskSkipResolver {
3107
3176
 
3108
3177
  // Update the conversation properties: name if it's the default, and set status back to 'Available'
3109
3178
  let needToSaveConvo = false;
3110
-
3179
+
3111
3180
  // Update name if still default
3112
3181
  if (convoEntity.Name === AskSkipResolver._defaultNewChatName && sTitle && sTitle !== AskSkipResolver._defaultNewChatName) {
3113
3182
  convoEntity.Name = sTitle; // use the title from the response
3114
3183
  needToSaveConvo = true;
3115
3184
  }
3116
-
3185
+
3117
3186
  // Set status back to 'Available' since processing is complete
3118
3187
  if (convoEntity.Status === 'Processing') {
3119
3188
  convoEntity.Status = 'Available';
3120
3189
  needToSaveConvo = true;
3121
3190
  }
3122
-
3191
+
3123
3192
  // Save if any changes were made
3124
3193
  if (needToSaveConvo) {
3125
3194
  const convoEntitySaveResult: boolean = await convoEntity.Save();
@@ -3180,44 +3249,49 @@ export class AskSkipResolver {
3180
3249
  };
3181
3250
  }
3182
3251
 
3183
- private async setConversationStatus(convoEntity: ConversationEntity, status: 'Processing' | 'Available', userPayload: UserPayload, pubSub?: PubSubEngine): Promise<boolean> {
3252
+ private async setConversationStatus(
3253
+ convoEntity: ConversationEntity,
3254
+ status: 'Processing' | 'Available',
3255
+ userPayload: UserPayload,
3256
+ pubSub?: PubSubEngine
3257
+ ): Promise<boolean> {
3184
3258
  if (convoEntity.Status !== status) {
3185
- convoEntity.Status = status;
3259
+ convoEntity.Status = status;
3186
3260
 
3187
- const convoSaveResult = await convoEntity.Save();
3188
- if (!convoSaveResult) {
3189
- LogError(`Error updating conversation status to '${status}'`, undefined, convoEntity.LatestResult);
3190
- } else {
3191
- // If conversation is now Available (completed), remove it from active streams
3192
- if (status === 'Available') {
3193
- activeStreams.removeConversation(convoEntity.ID);
3194
- LogStatus(`Removed conversation ${convoEntity.ID} from active streams (status changed to Available)`);
3195
- } else if (status === 'Processing') {
3196
- // If conversation is starting to process, add the session to active streams
3197
- activeStreams.addSession(convoEntity.ID, userPayload.sessionId);
3198
- LogStatus(`Added session ${userPayload.sessionId} to active streams for conversation ${convoEntity.ID}`);
3199
- }
3200
-
3201
- if (pubSub) {
3202
- // Publish status update to notify frontend of conversation status change
3203
- const statusMessage = {
3204
- type: 'ConversationStatusUpdate',
3205
- conversationID: convoEntity.ID,
3206
- status: status,
3207
- timestamp: new Date().toISOString()
3208
- };
3209
-
3210
- pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
3211
- pushStatusUpdates: {
3212
- message: JSON.stringify(statusMessage),
3213
- sessionId: userPayload.sessionId
3261
+ const convoSaveResult = await convoEntity.Save();
3262
+ if (!convoSaveResult) {
3263
+ LogError(`Error updating conversation status to '${status}'`, undefined, convoEntity.LatestResult);
3264
+ } else {
3265
+ // If conversation is now Available (completed), remove it from active streams
3266
+ if (status === 'Available') {
3267
+ activeStreams.removeConversation(convoEntity.ID);
3268
+ LogStatus(`Removed conversation ${convoEntity.ID} from active streams (status changed to Available)`);
3269
+ } else if (status === 'Processing') {
3270
+ // If conversation is starting to process, add the session to active streams
3271
+ activeStreams.addSession(convoEntity.ID, userPayload.sessionId);
3272
+ LogStatus(`Added session ${userPayload.sessionId} to active streams for conversation ${convoEntity.ID}`);
3273
+ }
3274
+
3275
+ if (pubSub) {
3276
+ // Publish status update to notify frontend of conversation status change
3277
+ const statusMessage = {
3278
+ type: 'ConversationStatusUpdate',
3279
+ conversationID: convoEntity.ID,
3280
+ status: status,
3281
+ timestamp: new Date().toISOString(),
3282
+ };
3283
+
3284
+ pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
3285
+ pushStatusUpdates: {
3286
+ message: JSON.stringify(statusMessage),
3287
+ sessionId: userPayload.sessionId,
3288
+ },
3289
+ });
3290
+
3291
+ LogStatus(`Published conversation status update for ${convoEntity.ID}: ${status}`);
3214
3292
  }
3215
- });
3216
-
3217
- LogStatus(`Published conversation status update for ${convoEntity.ID}: ${status}`);
3218
3293
  }
3219
- }
3220
- return convoSaveResult;
3294
+ return convoSaveResult;
3221
3295
  }
3222
3296
  return true;
3223
3297
  }
@@ -3225,19 +3299,20 @@ export class AskSkipResolver {
3225
3299
  /**
3226
3300
  * Gets the ID of an agent note type by its name
3227
3301
  * Falls back to a default note type if the specified one is not found
3228
- *
3302
+ *
3229
3303
  * @param name Name of the agent note type
3230
3304
  * @param defaultNoteType Default note type to use if the specified one is not found
3231
3305
  * @returns ID of the agent note type
3232
3306
  */
3233
3307
  protected getAgentNoteTypeIDByName(name: string, defaultNoteType: string = 'AI'): string {
3234
- const noteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === name.trim().toLowerCase())?.ID;
3235
- if (noteTypeID) {
3308
+ const noteTypeID = AIEngine.Instance.AgentNoteTypes.find((nt) => nt.Name.trim().toLowerCase() === name.trim().toLowerCase())?.ID;
3309
+ if (noteTypeID) {
3236
3310
  return noteTypeID;
3237
- }
3238
- else{
3239
- // default
3240
- const defaultNoteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === defaultNoteType.trim().toLowerCase())?.ID;
3311
+ } else {
3312
+ // default
3313
+ const defaultNoteTypeID = AIEngine.Instance.AgentNoteTypes.find(
3314
+ (nt) => nt.Name.trim().toLowerCase() === defaultNoteType.trim().toLowerCase()
3315
+ )?.ID;
3241
3316
  return defaultNoteTypeID;
3242
3317
  }
3243
3318
  }
@@ -3245,7 +3320,7 @@ export class AskSkipResolver {
3245
3320
  /**
3246
3321
  * Gets data from a view
3247
3322
  * Helper method to run a view and retrieve its data
3248
- *
3323
+ *
3249
3324
  * @param ViewId ID of the view to run
3250
3325
  * @param user User context for the query
3251
3326
  * @returns Results of the view query
@@ -3260,7 +3335,7 @@ export class AskSkipResolver {
3260
3335
  // /**
3261
3336
  // * Manually executes the Skip AI learning cycle
3262
3337
  // * Allows triggering a learning cycle on demand rather than waiting for scheduled execution
3263
- // *
3338
+ // *
3264
3339
  // * @param OrganizationId Optional organization ID to register for this run
3265
3340
  // * @returns Result of the manual learning cycle execution
3266
3341
  // */
@@ -3278,7 +3353,7 @@ export class AskSkipResolver {
3278
3353
  // Message: 'Learning cycles are not enabled in configuration'
3279
3354
  // };
3280
3355
  // }
3281
-
3356
+
3282
3357
  // // Check if we have a valid endpoint when cycles are enabled
3283
3358
  // const hasLearningEndpoint = (skipConfigInfo.url && skipConfigInfo.url.trim().length > 0) ||
3284
3359
  // (skipConfigInfo.learningCycleURL && skipConfigInfo.learningCycleURL.trim().length > 0);
@@ -3288,17 +3363,17 @@ export class AskSkipResolver {
3288
3363
  // Message: 'Learning cycle API endpoint is not configured'
3289
3364
  // };
3290
3365
  // }
3291
-
3366
+
3292
3367
  // // Use the organization ID from config if not provided
3293
3368
  // const orgId = OrganizationId || skipConfigInfo.orgID;
3294
-
3369
+
3295
3370
  // // Call the scheduler's manual execution method with org ID
3296
3371
  // const result = await LearningCycleScheduler.Instance.manuallyExecuteLearningCycle(orgId);
3297
-
3372
+
3298
3373
  // return {
3299
3374
  // Success: result,
3300
- // Message: result
3301
- // ? `Learning cycle was successfully executed manually for organization ${orgId}`
3375
+ // Message: result
3376
+ // ? `Learning cycle was successfully executed manually for organization ${orgId}`
3302
3377
  // : `Learning cycle execution failed for organization ${orgId}. Check server logs for details.`
3303
3378
  // };
3304
3379
  // }
@@ -3310,18 +3385,18 @@ export class AskSkipResolver {
3310
3385
  // };
3311
3386
  // }
3312
3387
  // }
3313
-
3388
+
3314
3389
  // /**
3315
3390
  // * Gets the current status of the learning cycle scheduler
3316
3391
  // * Provides information about the scheduler state and any running cycles
3317
- // *
3392
+ // *
3318
3393
  // * @returns Status information about the learning cycle scheduler
3319
3394
  // */
3320
3395
  // @Query(() => LearningCycleStatusType)
3321
3396
  // async GetLearningCycleStatus(): Promise<LearningCycleStatusType> {
3322
3397
  // try {
3323
3398
  // const status = LearningCycleScheduler.Instance.getStatus();
3324
-
3399
+
3325
3400
  // return {
3326
3401
  // IsSchedulerRunning: status.isSchedulerRunning,
3327
3402
  // LastRunTime: status.lastRunTime ? status.lastRunTime.toISOString() : null,
@@ -3342,11 +3417,11 @@ export class AskSkipResolver {
3342
3417
  // };
3343
3418
  // }
3344
3419
  // }
3345
-
3420
+
3346
3421
  // /**
3347
3422
  // * Checks if a specific organization is running a learning cycle
3348
3423
  // * Used to determine if a new learning cycle can be started for an organization
3349
- // *
3424
+ // *
3350
3425
  // * @param OrganizationId The organization ID to check
3351
3426
  // * @returns Information about the running cycle, or null if no cycle is running
3352
3427
  // */
@@ -3358,13 +3433,13 @@ export class AskSkipResolver {
3358
3433
  // const skipConfigInfo = configInfo.askSkip;
3359
3434
  // // Use the organization ID from config if not provided
3360
3435
  // const orgId = OrganizationId || skipConfigInfo.orgID;
3361
-
3436
+
3362
3437
  // const status = LearningCycleScheduler.Instance.isOrganizationRunningCycle(orgId);
3363
-
3438
+
3364
3439
  // if (!status.isRunning) {
3365
3440
  // return null;
3366
3441
  // }
3367
-
3442
+
3368
3443
  // return {
3369
3444
  // OrganizationId: orgId,
3370
3445
  // LearningCycleId: status.learningCycleId,
@@ -3377,11 +3452,11 @@ export class AskSkipResolver {
3377
3452
  // return null;
3378
3453
  // }
3379
3454
  // }
3380
-
3455
+
3381
3456
  // /**
3382
3457
  // * Stops a running learning cycle for a specific organization
3383
3458
  // * Allows manual intervention to stop a learning cycle that is taking too long or causing issues
3384
- // *
3459
+ // *
3385
3460
  // * @param OrganizationId The organization ID to stop the cycle for
3386
3461
  // * @returns Result of the stop operation, including details about the stopped cycle
3387
3462
  // */
@@ -3392,9 +3467,9 @@ export class AskSkipResolver {
3392
3467
  // try {
3393
3468
  // // Use the organization ID from config if not provided
3394
3469
  // const orgId = OrganizationId || configInfo.askSkip.orgID;
3395
-
3470
+
3396
3471
  // const result = LearningCycleScheduler.Instance.stopLearningCycleForOrganization(orgId);
3397
-
3472
+
3398
3473
  // // Transform the result to match our GraphQL type
3399
3474
  // return {
3400
3475
  // Success: result.success,
@@ -3417,7 +3492,6 @@ export class AskSkipResolver {
3417
3492
  // };
3418
3493
  // }
3419
3494
  // }
3420
-
3421
3495
  }
3422
3496
 
3423
3497
  export default AskSkipResolver;