@memberjunction/server 2.39.0 → 2.41.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.
@@ -22,7 +22,7 @@ import { LoadDataContextItemsServer } from '@memberjunction/data-context-server'
22
22
  import { LearningCycleScheduler } from '../scheduler/LearningCycleScheduler.js';
23
23
  LoadDataContextItemsServer();
24
24
  import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
25
- import { ___skipAPIOrgId, ___skipAPIurl, ___skipLearningAPIurl, ___skipLearningCycleIntervalInMinutes, ___skipRunLearningCycles, apiKey, baseUrl, configInfo, graphqlPort, mj_core_schema } from '../config.js';
25
+ import { apiKey, baseUrl, configInfo, graphqlPort, mj_core_schema } from '../config.js';
26
26
  import { registerEnumType } from 'type-graphql';
27
27
  import { MJGlobal, CopyScalarsAndArrays } from '@memberjunction/global';
28
28
  import { sendPostRequest } from '../util.js';
@@ -191,44 +191,60 @@ StopLearningCycleResultType = __decorate([
191
191
  ObjectType()
192
192
  ], StopLearningCycleResultType);
193
193
  export { StopLearningCycleResultType };
194
- let AskSkipResolver = class AskSkipResolver {
195
- static { AskSkipResolver_1 = this; }
196
- static _defaultNewChatName = 'New Chat';
197
- static {
198
- try {
199
- const eventListener = MJGlobal.Instance.GetEventListener(true);
200
- eventListener.subscribe(event => {
201
- if (event.eventCode === MJ_SERVER_EVENT_CODE && event.args?.type === 'setupComplete') {
202
- try {
203
- if (___skipRunLearningCycles !== 'Y') {
204
- LogStatus('Skip AI Learning cycle scheduler not started: Disabled in configuration');
205
- return;
206
- }
207
- if (!___skipLearningAPIurl || ___skipLearningAPIurl.trim() === '') {
208
- LogError('Skip AI Learning cycle scheduler not started: Learning cycles are enabled but no API endpoint is configured');
209
- return;
210
- }
211
- const dataSources = event.args.dataSources;
212
- if (dataSources && dataSources.length > 0) {
213
- const scheduler = LearningCycleScheduler.Instance;
214
- scheduler.setDataSources(dataSources);
215
- const interval = ___skipLearningCycleIntervalInMinutes ?? 60;
194
+ function initializeSkipLearningCycleScheduler() {
195
+ try {
196
+ const eventListener = MJGlobal.Instance.GetEventListener(true);
197
+ eventListener.subscribe(event => {
198
+ if (event.eventCode === MJ_SERVER_EVENT_CODE && event.args?.type === 'setupComplete') {
199
+ try {
200
+ const skipConfigInfo = configInfo.askSkip;
201
+ if (!skipConfigInfo) {
202
+ LogStatus('Skip AI Learning Cycle Scheduler not started: Skip configuration not found');
203
+ return;
204
+ }
205
+ if (!skipConfigInfo.learningCycleEnabled) {
206
+ LogStatus('Skip AI Learning Cycles not enabled in configuration');
207
+ return;
208
+ }
209
+ if (!skipConfigInfo.learningCycleURL || skipConfigInfo.learningCycleURL.trim().length === 0) {
210
+ LogError('Skip AI Learning cycle scheduler not started: Learning cycles are enabled but no Learning Cycle API endpoint is configured');
211
+ return;
212
+ }
213
+ const dataSources = event.args.dataSources;
214
+ if (dataSources && dataSources.length > 0) {
215
+ const scheduler = LearningCycleScheduler.Instance;
216
+ scheduler.setDataSources(dataSources);
217
+ const interval = skipConfigInfo.learningCycleIntervalInMinutes ?? 60;
218
+ if (skipConfigInfo.learningCycleRunUponStartup) {
219
+ LogStatus('Skip API Learning Cycle: Run Upon Startup is enabled, running learning cycle immediately');
216
220
  scheduler.start(interval);
217
221
  }
218
222
  else {
219
- LogError('Cannot initialize Skip learning cycle scheduler: No data sources available');
223
+ LogStatus(`Skip API Learning Cycle: Scheduler first run will start after interval of ${interval} minutes. If you want a learing cycle to run immediately, set the learningCycleRunUponStartup property in the config file to true.`);
224
+ setTimeout(() => {
225
+ LogStatus(`Skip API Learning Cycle: Starting scheduler after ${interval} minutes. If you want a learing cycle to run immediately, set the learningCycleRunUponStartup property in the config file to true.`);
226
+ scheduler.start(interval);
227
+ }, interval * 60 * 1000);
220
228
  }
221
229
  }
222
- catch (error) {
223
- LogError(`Error initializing Skip learning cycle scheduler: ${error}`);
230
+ else {
231
+ LogError('Cannot initialize Skip learning cycle scheduler: No data sources available');
224
232
  }
225
233
  }
226
- });
227
- }
228
- catch (error) {
229
- LogError(`Failed to initialize Skip learning cycle scheduler: ${error}`);
230
- }
234
+ catch (error) {
235
+ LogError(`Error initializing Skip learning cycle scheduler: ${error}`);
236
+ }
237
+ }
238
+ });
239
+ }
240
+ catch (error) {
241
+ LogError(`Failed to initialize Skip learning cycle scheduler: ${error}`);
231
242
  }
243
+ }
244
+ initializeSkipLearningCycleScheduler();
245
+ let AskSkipResolver = class AskSkipResolver {
246
+ static { AskSkipResolver_1 = this; }
247
+ static _defaultNewChatName = 'New Chat';
232
248
  static _maxHistoricalMessages = 30;
233
249
  async ExecuteAskSkipRecordChat(UserQuestion, ConversationId, EntityName, compositeKey, { dataSource, userPayload }, pubSub) {
234
250
  const user = UserCache.Instance.Users.find((u) => u.Email.trim().toLowerCase() === userPayload.email.trim().toLowerCase());
@@ -271,17 +287,18 @@ let AskSkipResolver = class AskSkipResolver {
271
287
  return this.handleSimpleSkipChatPostRequest(input, convoEntity.ID, convoDetailEntity.ID, true, user);
272
288
  }
273
289
  async ExecuteAskSkipLearningCycle({ dataSource, userPayload }, ForceEntityRefresh) {
274
- if (___skipRunLearningCycles !== 'Y') {
290
+ const skipConfigInfo = configInfo.askSkip;
291
+ if (!skipConfigInfo.learningCycleEnabled) {
275
292
  return {
276
293
  success: false,
277
- error: 'Learning cycles are disabled in configuration',
294
+ error: 'Learning cycles are not enabled in configuration',
278
295
  elapsedTime: 0,
279
296
  noteChanges: [],
280
297
  queryChanges: [],
281
298
  requestChanges: []
282
299
  };
283
300
  }
284
- if (!___skipLearningAPIurl || ___skipLearningAPIurl.trim() === '') {
301
+ if (!skipConfigInfo.learningCycleURL || skipConfigInfo.learningCycleURL.trim().length === 0) {
285
302
  return {
286
303
  success: false,
287
304
  error: 'Learning cycle API endpoint is not configured',
@@ -296,7 +313,7 @@ let AskSkipResolver = class AskSkipResolver {
296
313
  if (!user)
297
314
  throw new Error(`User ${userPayload.email} not found in UserCache`);
298
315
  await AIEngine.Instance.Config(false, user);
299
- const organizationId = ___skipAPIOrgId;
316
+ const organizationId = skipConfigInfo.orgID;
300
317
  const scheduler = LearningCycleScheduler.Instance;
301
318
  const runningStatus = scheduler.isOrganizationRunningCycle(organizationId);
302
319
  if (runningStatus.isRunning) {
@@ -331,17 +348,36 @@ let AskSkipResolver = class AskSkipResolver {
331
348
  try {
332
349
  LogStatus(`Building Skip Learning API request`);
333
350
  const input = await this.buildSkipLearningAPIRequest(learningCycleId, lastCompleteLearningCycleDate, true, true, true, false, dataSource, user, ForceEntityRefresh || false);
334
- const response = await this.handleSimpleSkipLearningPostRequest(input, user, learningCycleId, agentID);
335
- const endTime = new Date();
336
- const elapsedTimeMs = endTime.getTime() - startTime.getTime();
337
- LogStatus(`Learning cycle finished with status: ${response.success ? 'Success' : 'Failed'} in ${elapsedTimeMs / 1000} seconds`);
338
- learningCycleEntity.Status = response.success ? 'Complete' : 'Failed';
339
- learningCycleEntity.EndedAt = endTime;
340
- if (!(await learningCycleEntity.Save())) {
341
- LogError(`Failed to update learning cycle record: ${learningCycleEntity.LatestResult.Error}`);
351
+ if (input.newConversations.length === 0) {
352
+ LogStatus(` Skip Learning Cycles: No new conversations to process for learning cycle`);
353
+ learningCycleEntity.Status = 'Complete';
354
+ learningCycleEntity.AgentSummary = 'No new conversations to process, learning cycle skipped, but recorded for audit purposes.';
355
+ learningCycleEntity.EndedAt = new Date();
356
+ if (!(await learningCycleEntity.Save())) {
357
+ LogError(`Failed to update learning cycle record: ${learningCycleEntity.LatestResult.Error}`);
358
+ }
359
+ const result = {
360
+ success: true,
361
+ learningCycleSkipped: true,
362
+ elapsedTime: 0,
363
+ noteChanges: [],
364
+ queryChanges: [],
365
+ requestChanges: [],
366
+ };
367
+ return result;
368
+ }
369
+ else {
370
+ const response = await this.handleSimpleSkipLearningPostRequest(input, user, learningCycleId, agentID);
371
+ const endTime = new Date();
372
+ const elapsedTimeMs = endTime.getTime() - startTime.getTime();
373
+ LogStatus(`Learning cycle finished with status: ${response.success ? 'Success' : 'Failed'} in ${elapsedTimeMs / 1000} seconds`);
374
+ learningCycleEntity.Status = response.success ? 'Complete' : 'Failed';
375
+ learningCycleEntity.EndedAt = endTime;
376
+ if (!(await learningCycleEntity.Save())) {
377
+ LogError(`Failed to update learning cycle record: ${learningCycleEntity.LatestResult.Error}`);
378
+ }
379
+ return response;
342
380
  }
343
- scheduler.unregisterRunningCycle(organizationId);
344
- return response;
345
381
  }
346
382
  catch (error) {
347
383
  learningCycleEntity.Status = 'Failed';
@@ -352,13 +388,21 @@ let AskSkipResolver = class AskSkipResolver {
352
388
  catch (saveError) {
353
389
  LogError(`Failed to update learning cycle record after error: ${saveError}`);
354
390
  }
355
- scheduler.unregisterRunningCycle(organizationId);
356
391
  throw error;
357
392
  }
393
+ finally {
394
+ try {
395
+ scheduler.unregisterRunningCycle(organizationId);
396
+ }
397
+ catch (error) {
398
+ LogError(`Failed to unregister organization ${organizationId} from running cycles: ${error}`);
399
+ }
400
+ }
358
401
  }
359
402
  async handleSimpleSkipLearningPostRequest(input, user, learningCycleId, agentID) {
360
- LogStatus(` >>> HandleSimpleSkipLearningPostRequest Sending request to Skip API: ${___skipLearningAPIurl}`);
361
- const response = await sendPostRequest(___skipLearningAPIurl, input, true, null);
403
+ const skipConfigInfo = configInfo.askSkip;
404
+ LogStatus(` >>> HandleSimpleSkipLearningPostRequest Sending request to Skip API: ${skipConfigInfo.learningCycleURL}`);
405
+ const response = await sendPostRequest(skipConfigInfo.learningCycleURL, input, true, null);
362
406
  if (response && response.length > 0) {
363
407
  const apiResponse = response[response.length - 1].value;
364
408
  LogStatus(` Skip API response: ${apiResponse.success}`);
@@ -379,8 +423,9 @@ let AskSkipResolver = class AskSkipResolver {
379
423
  }
380
424
  }
381
425
  async handleSimpleSkipChatPostRequest(input, conversationID = '', UserMessageConversationDetailId = '', createAIMessageConversationDetail = false, user = null) {
382
- LogStatus(` >>> HandleSimpleSkipChatPostRequest Sending request to Skip API: ${___skipAPIurl}`);
383
- const response = await sendPostRequest(___skipAPIurl, input, true, null);
426
+ const skipConfigInfo = configInfo.askSkip;
427
+ LogStatus(` >>> HandleSimpleSkipChatPostRequest Sending request to Skip API: ${skipConfigInfo.chatURL}`);
428
+ const response = await sendPostRequest(skipConfigInfo.chatURL, input, true, null);
384
429
  if (response && response.length > 0) {
385
430
  const apiResponse = response[response.length - 1].value;
386
431
  const AIMessageConversationDetailID = createAIMessageConversationDetail
@@ -445,10 +490,6 @@ let AskSkipResolver = class AskSkipResolver {
445
490
  }
446
491
  }
447
492
  else {
448
- if (change.note.agentNoteType === "Human") {
449
- LogStatus(`WARNING: Cannot create a new Human note with the learning cycle. Operation ignored.`);
450
- return false;
451
- }
452
493
  noteEntity.NewRecord();
453
494
  noteEntity.AgentID = agentID;
454
495
  }
@@ -506,10 +547,11 @@ cycle.`);
506
547
  return '';
507
548
  }
508
549
  }
509
- async buildBaseSkipRequest(contextUser, dataSource, includeEntities, includeQueries, includeNotes, includeRequests, forceEntitiesRefresh = false, includeCallBackKeyAndAccessToken = false, additionalTokenInfo = {}) {
550
+ async buildBaseSkipRequest(contextUser, dataSource, includeEntities, includeQueries, includeNotes, filterUserNotesToContextUser, includeRequests, forceEntitiesRefresh = false, includeCallBackKeyAndAccessToken = false, additionalTokenInfo = {}) {
551
+ const skipConfigInfo = configInfo.askSkip;
510
552
  const entities = includeEntities ? await this.BuildSkipEntities(dataSource, forceEntitiesRefresh) : [];
511
553
  const queries = includeQueries ? this.BuildSkipQueries() : [];
512
- const { notes, noteTypes } = includeNotes ? await this.BuildSkipAgentNotes(contextUser) : { notes: [], noteTypes: [] };
554
+ const { notes, noteTypes } = includeNotes ? await this.BuildSkipAgentNotes(contextUser, filterUserNotesToContextUser) : { notes: [], noteTypes: [] };
513
555
  const requests = includeRequests ? await this.BuildSkipRequests(contextUser) : [];
514
556
  let accessToken;
515
557
  if (includeCallBackKeyAndAccessToken) {
@@ -529,7 +571,7 @@ cycle.`);
529
571
  noteTypes,
530
572
  requests,
531
573
  accessToken,
532
- organizationID: ___skipAPIOrgId,
574
+ organizationID: skipConfigInfo.orgID,
533
575
  organizationInfo: configInfo?.askSkip?.organizationInfo,
534
576
  apiKeys: this.buildSkipAPIKeys(),
535
577
  callingServerURL: accessToken ? `${baseUrl}:${graphqlPort}` : undefined,
@@ -538,7 +580,7 @@ cycle.`);
538
580
  };
539
581
  }
540
582
  async buildSkipLearningAPIRequest(learningCycleId, lastLearningCycleDate, includeEntities, includeQueries, includeNotes, includeRequests, dataSource, contextUser, forceEntitiesRefresh = false, includeCallBackKeyAndAccessToken = false) {
541
- const baseRequest = await this.buildBaseSkipRequest(contextUser, dataSource, includeEntities, includeQueries, includeNotes, includeRequests, forceEntitiesRefresh, includeCallBackKeyAndAccessToken);
583
+ const baseRequest = await this.buildBaseSkipRequest(contextUser, dataSource, includeEntities, includeQueries, includeNotes, false, includeRequests, forceEntitiesRefresh, includeCallBackKeyAndAccessToken);
542
584
  const newConversations = await this.BuildSkipLearningCycleNewConversations(lastLearningCycleDate, dataSource, contextUser);
543
585
  const input = {
544
586
  organizationId: baseRequest.organizationID,
@@ -638,7 +680,7 @@ cycle.`);
638
680
  conversationId,
639
681
  requestPhase,
640
682
  };
641
- const baseRequest = await this.buildBaseSkipRequest(contextUser, dataSource, includeEntities, includeQueries, includeNotes, includeRequests, forceEntitiesRefresh, includeCallBackKeyAndAccessToken, additionalTokenInfo);
683
+ const baseRequest = await this.buildBaseSkipRequest(contextUser, dataSource, includeEntities, includeQueries, includeNotes, true, includeRequests, forceEntitiesRefresh, includeCallBackKeyAndAccessToken, additionalTokenInfo);
642
684
  const artifacts = await this.buildSkipAPIArtifacts(contextUser, dataSource, conversationId);
643
685
  const input = {
644
686
  ...baseRequest,
@@ -753,16 +795,18 @@ cycle.`);
753
795
  },
754
796
  ];
755
797
  }
756
- async ExecuteAskSkipAnalysisQuery(UserQuestion, ConversationId, { dataSource, userPayload }, pubSub, DataContextId, ForceEntityRefresh) {
798
+ async ExecuteAskSkipAnalysisQuery(UserQuestion, ConversationId, { dataSource, userPayload }, pubSub, DataContextId, ForceEntityRefresh, StartTime) {
757
799
  const md = new Metadata();
758
800
  const user = UserCache.Instance.Users.find((u) => u.Email.trim().toLowerCase() === userPayload.email.trim().toLowerCase());
759
801
  if (!user)
760
802
  throw new Error(`User ${userPayload.email} not found in UserCache`);
803
+ const requestStartTime = StartTime || new Date();
761
804
  const { convoEntity, dataContextEntity, convoDetailEntity, dataContext } = await this.HandleSkipChatInitialObjectLoading(dataSource, ConversationId, UserQuestion, user, userPayload, md, DataContextId);
805
+ this.setConversationStatus(convoEntity, 'Processing');
762
806
  const messages = await this.LoadConversationDetailsIntoSkipMessages(dataSource, convoEntity.ID, AskSkipResolver_1._maxHistoricalMessages);
763
807
  const conversationDetailCount = 1;
764
808
  const input = await this.buildSkipChatAPIRequest(messages, ConversationId, dataContext, 'initial_request', true, true, true, false, user, dataSource, ForceEntityRefresh === undefined ? false : ForceEntityRefresh, true);
765
- return this.HandleSkipChatRequest(input, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount);
809
+ return this.HandleSkipChatRequest(input, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount, requestStartTime);
766
810
  }
767
811
  BuildSkipQueries(status = 'Approved') {
768
812
  const md = new Metadata();
@@ -804,7 +848,7 @@ cycle.`);
804
848
  };
805
849
  });
806
850
  }
807
- async BuildSkipAgentNotes(contextUser) {
851
+ async BuildSkipAgentNotes(contextUser, filterUserNotesToContextUser) {
808
852
  try {
809
853
  await AIEngine.Instance.Config(false, contextUser);
810
854
  const agent = AIEngine.Instance.GetAgentByName('Skip');
@@ -824,6 +868,10 @@ cycle.`);
824
868
  updatedAt: r.__mj_UpdatedAt,
825
869
  };
826
870
  });
871
+ if (filterUserNotesToContextUser) {
872
+ notes = notes.filter((n) => n.type === 'Global' ||
873
+ (n.type === 'User' && n.userId === contextUser.ID));
874
+ }
827
875
  noteTypes = AIEngine.Instance.AgentNoteTypes.map((r) => {
828
876
  return {
829
877
  id: r.ID,
@@ -951,7 +999,7 @@ cycle.`);
951
999
  async refreshSkipEntities(dataSource) {
952
1000
  try {
953
1001
  const md = new Metadata();
954
- const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSendSkip?.includeEntitiesFromExcludedSchemas ?? [])
1002
+ const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas ?? [])
955
1003
  .map((e) => e.trim().toLowerCase());
956
1004
  const entities = md.Entities.filter((e) => {
957
1005
  if (e.SchemaName !== mj_core_schema || skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())) {
@@ -1087,6 +1135,7 @@ cycle.`);
1087
1135
  if (user) {
1088
1136
  convoEntity.UserID = user.ID;
1089
1137
  convoEntity.Name = AskSkipResolver_1._defaultNewChatName;
1138
+ convoEntity.Status = 'Available';
1090
1139
  dataContextEntity = await md.GetEntityObject('Data Contexts', user);
1091
1140
  if (!DataContextId || DataContextId.length === 0) {
1092
1141
  dataContextEntity.NewRecord();
@@ -1172,19 +1221,20 @@ cycle.`);
1172
1221
  await dataContext.Load(dataContextEntity.ID, dataSource, false, false, 0, user);
1173
1222
  return { dataContext, convoEntity, dataContextEntity, convoDetailEntity };
1174
1223
  }
1175
- async LoadConversationDetailsIntoSkipMessages(dataSource, ConversationId, maxHistoricalMessages) {
1224
+ async LoadConversationDetailsIntoSkipMessages(dataSource, ConversationId, maxHistoricalMessages, roleFilter) {
1176
1225
  try {
1177
1226
  if (!ConversationId || ConversationId.length === 0) {
1178
1227
  throw new Error(`ConversationId is required`);
1179
1228
  }
1180
1229
  const md = new Metadata();
1181
1230
  const e = md.Entities.find((e) => e.Name === 'Conversation Details');
1231
+ const roleFilterClause = roleFilter ? ` AND Role = '${roleFilter}'` : '';
1182
1232
  const sql = `SELECT
1183
1233
  ${maxHistoricalMessages ? 'TOP ' + maxHistoricalMessages : ''} *
1184
1234
  FROM
1185
1235
  ${e.SchemaName}.${e.BaseView}
1186
1236
  WHERE
1187
- ConversationID = '${ConversationId}'
1237
+ ConversationID = '${ConversationId}'${roleFilterClause}
1188
1238
  ORDER
1189
1239
  BY __mj_CreatedAt DESC`;
1190
1240
  const result = await dataSource.query(sql);
@@ -1260,9 +1310,11 @@ cycle.`);
1260
1310
  return 'user';
1261
1311
  }
1262
1312
  }
1263
- async HandleSkipChatRequest(input, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount) {
1264
- LogStatus(` >>> HandleSkipRequest: Sending request to Skip API: ${___skipAPIurl}`);
1313
+ async HandleSkipChatRequest(input, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount, startTime) {
1314
+ const skipConfigInfo = configInfo.askSkip;
1315
+ LogStatus(` >>> HandleSkipRequest: Sending request to Skip API: ${skipConfigInfo.chatURL}`);
1265
1316
  if (conversationDetailCount > 10) {
1317
+ await this.setConversationStatus(convoEntity, 'Available');
1266
1318
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
1267
1319
  message: JSON.stringify({
1268
1320
  type: 'AskSkip',
@@ -1282,7 +1334,7 @@ cycle.`);
1282
1334
  AIMessageConversationDetailId: '',
1283
1335
  };
1284
1336
  }
1285
- const response = await sendPostRequest(___skipAPIurl, input, true, null, (message) => {
1337
+ const response = await sendPostRequest(skipConfigInfo.chatURL, input, true, null, (message) => {
1286
1338
  LogStatus(JSON.stringify(message, null, 4));
1287
1339
  if (message.type === 'status_update') {
1288
1340
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
@@ -1302,19 +1354,20 @@ cycle.`);
1302
1354
  LogStatus(` Skip API response: ${apiResponse.responsePhase}`);
1303
1355
  this.PublishApiResponseUserUpdateMessage(apiResponse, userPayload, ConversationId, pubSub);
1304
1356
  if (apiResponse.responsePhase === 'data_request') {
1305
- return await this.HandleDataRequestPhase(input, apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount);
1357
+ return await this.HandleDataRequestPhase(input, apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount, startTime);
1306
1358
  }
1307
1359
  else if (apiResponse.responsePhase === 'clarifying_question') {
1308
- return await this.HandleClarifyingQuestionPhase(input, apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity);
1360
+ return await this.HandleClarifyingQuestionPhase(input, apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity, startTime);
1309
1361
  }
1310
1362
  else if (apiResponse.responsePhase === 'analysis_complete') {
1311
- return await this.HandleAnalysisComplete(input, apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity, dataContext, dataContextEntity);
1363
+ return await this.HandleAnalysisComplete(input, apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity, dataContext, dataContextEntity, startTime);
1312
1364
  }
1313
1365
  else {
1314
1366
  throw new Error(`Unknown Skip API response phase: ${apiResponse.responsePhase}`);
1315
1367
  }
1316
1368
  }
1317
1369
  else {
1370
+ await this.setConversationStatus(convoEntity, 'Available');
1318
1371
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
1319
1372
  message: JSON.stringify({
1320
1373
  type: 'AskSkip',
@@ -1357,12 +1410,12 @@ cycle.`);
1357
1410
  sessionId: userPayload.sessionId,
1358
1411
  });
1359
1412
  }
1360
- async HandleAnalysisComplete(apiRequest, apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity, dataContext, dataContextEntity) {
1413
+ async HandleAnalysisComplete(apiRequest, apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity, dataContext, dataContextEntity, startTime) {
1361
1414
  const md = new Metadata();
1362
1415
  if (apiRequest.callingServerAccessToken && tokenExists(apiRequest.callingServerAccessToken)) {
1363
1416
  deleteAccessToken(apiRequest.callingServerAccessToken);
1364
1417
  }
1365
- const { AIMessageConversationDetailID } = await this.FinishConversationAndNotifyUser(apiResponse, dataContext, dataContextEntity, md, user, convoEntity, pubSub, userPayload, dataSource);
1418
+ const { AIMessageConversationDetailID } = await this.FinishConversationAndNotifyUser(apiResponse, dataContext, dataContextEntity, md, user, convoEntity, pubSub, userPayload, dataSource, startTime);
1366
1419
  const response = {
1367
1420
  Success: true,
1368
1421
  Status: 'OK',
@@ -1374,7 +1427,8 @@ cycle.`);
1374
1427
  };
1375
1428
  return response;
1376
1429
  }
1377
- async HandleClarifyingQuestionPhase(apiRequest, apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity) {
1430
+ async HandleClarifyingQuestionPhase(apiRequest, apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity, startTime) {
1431
+ const endTime = new Date();
1378
1432
  const md = new Metadata();
1379
1433
  const convoDetailEntityAI = await md.GetEntityObject('Conversation Details', user);
1380
1434
  convoDetailEntityAI.NewRecord();
@@ -1382,6 +1436,8 @@ cycle.`);
1382
1436
  convoDetailEntityAI.Message = JSON.stringify(apiResponse);
1383
1437
  convoDetailEntityAI.Role = 'AI';
1384
1438
  convoDetailEntityAI.HiddenToUser = false;
1439
+ convoDetailEntityAI.CompletionTime = endTime.getTime() - startTime.getTime();
1440
+ this.setConversationStatus(convoEntity, 'Available');
1385
1441
  if (await convoDetailEntityAI.Save()) {
1386
1442
  return {
1387
1443
  Success: true,
@@ -1406,7 +1462,7 @@ cycle.`);
1406
1462
  };
1407
1463
  }
1408
1464
  }
1409
- async HandleDataRequestPhase(apiRequest, apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount) {
1465
+ async HandleDataRequestPhase(apiRequest, apiResponse, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount, startTime) {
1410
1466
  try {
1411
1467
  if (!apiResponse.success) {
1412
1468
  LogError(`Data request/gathering from Skip API failed: ${apiResponse.error}`);
@@ -1512,14 +1568,14 @@ cycle.`);
1512
1568
  apiRequest.requestPhase = 'data_gathering_response';
1513
1569
  }
1514
1570
  conversationDetailCount++;
1515
- return this.HandleSkipChatRequest(apiRequest, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount);
1571
+ return this.HandleSkipChatRequest(apiRequest, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount, startTime);
1516
1572
  }
1517
1573
  catch (e) {
1518
1574
  LogError(e);
1519
1575
  throw e;
1520
1576
  }
1521
1577
  }
1522
- async FinishConversationAndNotifyUser(apiResponse, dataContext, dataContextEntity, md, user, convoEntity, pubSub, userPayload, dataSource) {
1578
+ async FinishConversationAndNotifyUser(apiResponse, dataContext, dataContextEntity, md, user, convoEntity, pubSub, userPayload, dataSource, startTime) {
1523
1579
  const sTitle = apiResponse.reportTitle;
1524
1580
  const sResult = JSON.stringify(apiResponse);
1525
1581
  let artifactId = null;
@@ -1574,12 +1630,14 @@ cycle.`);
1574
1630
  }
1575
1631
  }
1576
1632
  }
1633
+ const endTime = new Date();
1577
1634
  const convoDetailEntityAI = await md.GetEntityObject('Conversation Details', user);
1578
1635
  convoDetailEntityAI.NewRecord();
1579
1636
  convoDetailEntityAI.ConversationID = convoEntity.ID;
1580
1637
  convoDetailEntityAI.Message = sResult;
1581
1638
  convoDetailEntityAI.Role = 'AI';
1582
1639
  convoDetailEntityAI.HiddenToUser = false;
1640
+ convoDetailEntityAI.CompletionTime = endTime.getTime() - startTime.getTime();
1583
1641
  if (artifactId && artifactId.length > 0) {
1584
1642
  convoDetailEntityAI.ArtifactID = artifactId;
1585
1643
  if (artifactVersionId && artifactVersionId.length > 0) {
@@ -1590,8 +1648,16 @@ cycle.`);
1590
1648
  if (!convoDetailSaveResult) {
1591
1649
  LogError(`Error saving conversation detail entity for AI message: ${sResult}`, undefined, convoDetailEntityAI.LatestResult);
1592
1650
  }
1651
+ let needToSaveConvo = false;
1593
1652
  if (convoEntity.Name === AskSkipResolver_1._defaultNewChatName && sTitle && sTitle !== AskSkipResolver_1._defaultNewChatName) {
1594
1653
  convoEntity.Name = sTitle;
1654
+ needToSaveConvo = true;
1655
+ }
1656
+ if (convoEntity.Status === 'Processing') {
1657
+ convoEntity.Status = 'Available';
1658
+ needToSaveConvo = true;
1659
+ }
1660
+ if (needToSaveConvo) {
1595
1661
  const convoEntitySaveResult = await convoEntity.Save();
1596
1662
  if (!convoEntitySaveResult) {
1597
1663
  LogError(`Error saving conversation entity for AI message: ${sResult}`, undefined, convoEntity.LatestResult);
@@ -1636,14 +1702,25 @@ cycle.`);
1636
1702
  AIMessageConversationDetailID: convoDetailEntityAI.ID,
1637
1703
  };
1638
1704
  }
1639
- getAgentNoteTypeIDByName(name) {
1705
+ async setConversationStatus(convoEntity, status) {
1706
+ if (convoEntity.Status !== status) {
1707
+ convoEntity.Status = status;
1708
+ const convoSaveResult = await convoEntity.Save();
1709
+ if (!convoSaveResult) {
1710
+ LogError(`Error updating conversation status to '${status}'`, undefined, convoEntity.LatestResult);
1711
+ }
1712
+ return convoSaveResult;
1713
+ }
1714
+ return true;
1715
+ }
1716
+ getAgentNoteTypeIDByName(name, defaultNoteType = 'AI') {
1640
1717
  const noteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === name.trim().toLowerCase())?.ID;
1641
1718
  if (noteTypeID) {
1642
1719
  return noteTypeID;
1643
1720
  }
1644
1721
  else {
1645
- const AINoteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === 'AI')?.ID;
1646
- return AINoteTypeID;
1722
+ const defaultNoteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === defaultNoteType.trim().toLowerCase())?.ID;
1723
+ return defaultNoteTypeID;
1647
1724
  }
1648
1725
  }
1649
1726
  async getViewData(ViewId, user) {
@@ -1657,19 +1734,20 @@ cycle.`);
1657
1734
  async ManuallyExecuteSkipLearningCycle(OrganizationId) {
1658
1735
  try {
1659
1736
  LogStatus('Manual execution of Skip learning cycle requested via API');
1660
- if (___skipRunLearningCycles !== 'Y') {
1737
+ const skipConfigInfo = configInfo.askSkip;
1738
+ if (!skipConfigInfo.learningCycleEnabled) {
1661
1739
  return {
1662
1740
  Success: false,
1663
- Message: 'Learning cycles are disabled in configuration'
1741
+ Message: 'Learning cycles are not enabled in configuration'
1664
1742
  };
1665
1743
  }
1666
- if (!___skipLearningAPIurl || ___skipLearningAPIurl.trim() === '') {
1744
+ if (!skipConfigInfo.learningCycleURL || skipConfigInfo.learningCycleURL.trim().length === 0) {
1667
1745
  return {
1668
1746
  Success: false,
1669
1747
  Message: 'Learning cycle API endpoint is not configured'
1670
1748
  };
1671
1749
  }
1672
- const orgId = OrganizationId || ___skipAPIOrgId;
1750
+ const orgId = OrganizationId || skipConfigInfo.orgID;
1673
1751
  const result = await LearningCycleScheduler.Instance.manuallyExecuteLearningCycle(orgId);
1674
1752
  return {
1675
1753
  Success: result,
@@ -1711,7 +1789,8 @@ cycle.`);
1711
1789
  }
1712
1790
  async IsOrganizationRunningLearningCycle(OrganizationId) {
1713
1791
  try {
1714
- const orgId = OrganizationId || ___skipAPIOrgId;
1792
+ const skipConfigInfo = configInfo.askSkip;
1793
+ const orgId = OrganizationId || skipConfigInfo.orgID;
1715
1794
  const status = LearningCycleScheduler.Instance.isOrganizationRunningCycle(orgId);
1716
1795
  if (!status.isRunning) {
1717
1796
  return null;
@@ -1730,7 +1809,7 @@ cycle.`);
1730
1809
  }
1731
1810
  async StopLearningCycleForOrganization(OrganizationId) {
1732
1811
  try {
1733
- const orgId = OrganizationId || ___skipAPIOrgId;
1812
+ const orgId = OrganizationId || configInfo.askSkip.orgID;
1734
1813
  const result = LearningCycleScheduler.Instance.stopLearningCycleForOrganization(orgId);
1735
1814
  return {
1736
1815
  Success: result.success,
@@ -1792,8 +1871,9 @@ __decorate([
1792
1871
  __param(3, PubSub()),
1793
1872
  __param(4, Arg('DataContextId', () => String, { nullable: true })),
1794
1873
  __param(5, Arg('ForceEntityRefresh', () => Boolean, { nullable: true })),
1874
+ __param(6, Arg('StartTime', () => Date, { nullable: true })),
1795
1875
  __metadata("design:type", Function),
1796
- __metadata("design:paramtypes", [String, String, Object, PubSubEngine, String, Boolean]),
1876
+ __metadata("design:paramtypes", [String, String, Object, PubSubEngine, String, Boolean, Date]),
1797
1877
  __metadata("design:returntype", Promise)
1798
1878
  ], AskSkipResolver.prototype, "ExecuteAskSkipAnalysisQuery", null);
1799
1879
  __decorate([