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