@memberjunction/server 2.39.0 → 2.40.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
+ });
231
239
  }
240
+ catch (error) {
241
+ LogError(`Failed to initialize Skip learning cycle scheduler: ${error}`);
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,
@@ -804,7 +846,7 @@ cycle.`);
804
846
  };
805
847
  });
806
848
  }
807
- async BuildSkipAgentNotes(contextUser) {
849
+ async BuildSkipAgentNotes(contextUser, filterUserNotesToContextUser) {
808
850
  try {
809
851
  await AIEngine.Instance.Config(false, contextUser);
810
852
  const agent = AIEngine.Instance.GetAgentByName('Skip');
@@ -824,6 +866,10 @@ cycle.`);
824
866
  updatedAt: r.__mj_UpdatedAt,
825
867
  };
826
868
  });
869
+ if (filterUserNotesToContextUser) {
870
+ notes = notes.filter((n) => n.type === 'Global' ||
871
+ (n.type === 'User' && n.userId === contextUser.ID));
872
+ }
827
873
  noteTypes = AIEngine.Instance.AgentNoteTypes.map((r) => {
828
874
  return {
829
875
  id: r.ID,
@@ -951,7 +997,7 @@ cycle.`);
951
997
  async refreshSkipEntities(dataSource) {
952
998
  try {
953
999
  const md = new Metadata();
954
- const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSendSkip?.includeEntitiesFromExcludedSchemas ?? [])
1000
+ const skipSpecialIncludeEntities = (configInfo.askSkip?.entitiesToSend?.includeEntitiesFromExcludedSchemas ?? [])
955
1001
  .map((e) => e.trim().toLowerCase());
956
1002
  const entities = md.Entities.filter((e) => {
957
1003
  if (e.SchemaName !== mj_core_schema || skipSpecialIncludeEntities.includes(e.Name.trim().toLowerCase())) {
@@ -1261,7 +1307,8 @@ cycle.`);
1261
1307
  }
1262
1308
  }
1263
1309
  async HandleSkipChatRequest(input, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount) {
1264
- LogStatus(` >>> HandleSkipRequest: Sending request to Skip API: ${___skipAPIurl}`);
1310
+ const skipConfigInfo = configInfo.askSkip;
1311
+ LogStatus(` >>> HandleSkipRequest: Sending request to Skip API: ${skipConfigInfo.chatURL}`);
1265
1312
  if (conversationDetailCount > 10) {
1266
1313
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
1267
1314
  message: JSON.stringify({
@@ -1282,7 +1329,7 @@ cycle.`);
1282
1329
  AIMessageConversationDetailId: '',
1283
1330
  };
1284
1331
  }
1285
- const response = await sendPostRequest(___skipAPIurl, input, true, null, (message) => {
1332
+ const response = await sendPostRequest(skipConfigInfo.chatURL, input, true, null, (message) => {
1286
1333
  LogStatus(JSON.stringify(message, null, 4));
1287
1334
  if (message.type === 'status_update') {
1288
1335
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
@@ -1636,14 +1683,14 @@ cycle.`);
1636
1683
  AIMessageConversationDetailID: convoDetailEntityAI.ID,
1637
1684
  };
1638
1685
  }
1639
- getAgentNoteTypeIDByName(name) {
1686
+ getAgentNoteTypeIDByName(name, defaultNoteType = 'AI') {
1640
1687
  const noteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === name.trim().toLowerCase())?.ID;
1641
1688
  if (noteTypeID) {
1642
1689
  return noteTypeID;
1643
1690
  }
1644
1691
  else {
1645
- const AINoteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === 'AI')?.ID;
1646
- return AINoteTypeID;
1692
+ const defaultNoteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === defaultNoteType.trim().toLowerCase())?.ID;
1693
+ return defaultNoteTypeID;
1647
1694
  }
1648
1695
  }
1649
1696
  async getViewData(ViewId, user) {
@@ -1657,19 +1704,20 @@ cycle.`);
1657
1704
  async ManuallyExecuteSkipLearningCycle(OrganizationId) {
1658
1705
  try {
1659
1706
  LogStatus('Manual execution of Skip learning cycle requested via API');
1660
- if (___skipRunLearningCycles !== 'Y') {
1707
+ const skipConfigInfo = configInfo.askSkip;
1708
+ if (!skipConfigInfo.learningCycleEnabled) {
1661
1709
  return {
1662
1710
  Success: false,
1663
- Message: 'Learning cycles are disabled in configuration'
1711
+ Message: 'Learning cycles are not enabled in configuration'
1664
1712
  };
1665
1713
  }
1666
- if (!___skipLearningAPIurl || ___skipLearningAPIurl.trim() === '') {
1714
+ if (!skipConfigInfo.learningCycleURL || skipConfigInfo.learningCycleURL.trim().length === 0) {
1667
1715
  return {
1668
1716
  Success: false,
1669
1717
  Message: 'Learning cycle API endpoint is not configured'
1670
1718
  };
1671
1719
  }
1672
- const orgId = OrganizationId || ___skipAPIOrgId;
1720
+ const orgId = OrganizationId || skipConfigInfo.orgID;
1673
1721
  const result = await LearningCycleScheduler.Instance.manuallyExecuteLearningCycle(orgId);
1674
1722
  return {
1675
1723
  Success: result,
@@ -1711,7 +1759,8 @@ cycle.`);
1711
1759
  }
1712
1760
  async IsOrganizationRunningLearningCycle(OrganizationId) {
1713
1761
  try {
1714
- const orgId = OrganizationId || ___skipAPIOrgId;
1762
+ const skipConfigInfo = configInfo.askSkip;
1763
+ const orgId = OrganizationId || skipConfigInfo.orgID;
1715
1764
  const status = LearningCycleScheduler.Instance.isOrganizationRunningCycle(orgId);
1716
1765
  if (!status.isRunning) {
1717
1766
  return null;
@@ -1730,7 +1779,7 @@ cycle.`);
1730
1779
  }
1731
1780
  async StopLearningCycleForOrganization(OrganizationId) {
1732
1781
  try {
1733
- const orgId = OrganizationId || ___skipAPIOrgId;
1782
+ const orgId = OrganizationId || configInfo.askSkip.orgID;
1734
1783
  const result = LearningCycleScheduler.Instance.stopLearningCycleForOrganization(orgId);
1735
1784
  return {
1736
1785
  Success: result.success,