@memberjunction/server 2.35.1 → 2.36.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +15 -1
  2. package/dist/config.d.ts +69 -1
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +11 -1
  5. package/dist/config.js.map +1 -1
  6. package/dist/generated/generated.d.ts +15 -12
  7. package/dist/generated/generated.d.ts.map +1 -1
  8. package/dist/generated/generated.js +73 -58
  9. package/dist/generated/generated.js.map +1 -1
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +41 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/resolvers/AskSkipResolver.d.ts +60 -5
  15. package/dist/resolvers/AskSkipResolver.d.ts.map +1 -1
  16. package/dist/resolvers/AskSkipResolver.js +587 -31
  17. package/dist/resolvers/AskSkipResolver.js.map +1 -1
  18. package/dist/rest/EntityCRUDHandler.d.ts +29 -0
  19. package/dist/rest/EntityCRUDHandler.d.ts.map +1 -0
  20. package/dist/rest/EntityCRUDHandler.js +197 -0
  21. package/dist/rest/EntityCRUDHandler.js.map +1 -0
  22. package/dist/rest/RESTEndpointHandler.d.ts +41 -0
  23. package/dist/rest/RESTEndpointHandler.d.ts.map +1 -0
  24. package/dist/rest/RESTEndpointHandler.js +537 -0
  25. package/dist/rest/RESTEndpointHandler.js.map +1 -0
  26. package/dist/rest/ViewOperationsHandler.d.ts +21 -0
  27. package/dist/rest/ViewOperationsHandler.d.ts.map +1 -0
  28. package/dist/rest/ViewOperationsHandler.js +144 -0
  29. package/dist/rest/ViewOperationsHandler.js.map +1 -0
  30. package/dist/rest/index.d.ts +5 -0
  31. package/dist/rest/index.d.ts.map +1 -0
  32. package/dist/rest/index.js +5 -0
  33. package/dist/rest/index.js.map +1 -0
  34. package/dist/rest/setupRESTEndpoints.d.ts +12 -0
  35. package/dist/rest/setupRESTEndpoints.d.ts.map +1 -0
  36. package/dist/rest/setupRESTEndpoints.js +27 -0
  37. package/dist/rest/setupRESTEndpoints.js.map +1 -0
  38. package/dist/scheduler/LearningCycleScheduler.d.ts +44 -0
  39. package/dist/scheduler/LearningCycleScheduler.d.ts.map +1 -0
  40. package/dist/scheduler/LearningCycleScheduler.js +188 -0
  41. package/dist/scheduler/LearningCycleScheduler.js.map +1 -0
  42. package/package.json +24 -26
  43. package/src/config.ts +15 -1
  44. package/src/generated/generated.ts +53 -44
  45. package/src/index.ts +56 -1
  46. package/src/resolvers/AskSkipResolver.ts +787 -51
  47. package/src/rest/EntityCRUDHandler.ts +279 -0
  48. package/src/rest/RESTEndpointHandler.ts +834 -0
  49. package/src/rest/ViewOperationsHandler.ts +207 -0
  50. package/src/rest/index.ts +4 -0
  51. package/src/rest/setupRESTEndpoints.ts +89 -0
  52. package/src/scheduler/LearningCycleScheduler.ts +312 -0
@@ -11,16 +11,18 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
11
11
  return function (target, key) { decorator(target, key, paramIndex); }
12
12
  };
13
13
  var AskSkipResolver_1;
14
- import { Arg, Ctx, Field, ObjectType, PubSub, PubSubEngine, Query, Resolver } from 'type-graphql';
14
+ import { Arg, Ctx, Field, Mutation, ObjectType, PubSub, PubSubEngine, Query, Resolver } from 'type-graphql';
15
15
  import { LogError, LogStatus, Metadata, RunView, CompositeKey } from '@memberjunction/core';
16
+ import { MJ_SERVER_EVENT_CODE } from '../types.js';
16
17
  import { BehaviorSubject } from 'rxjs';
17
18
  import { take } from 'rxjs/operators';
18
19
  import { UserCache } from '@memberjunction/sqlserver-dataprovider';
19
20
  import { DataContext } from '@memberjunction/data-context';
20
21
  import { LoadDataContextItemsServer } from '@memberjunction/data-context-server';
22
+ import { LearningCycleScheduler } from '../scheduler/LearningCycleScheduler.js';
21
23
  LoadDataContextItemsServer();
22
24
  import { PUSH_STATUS_UPDATES_TOPIC } from '../generic/PushStatusResolver.js';
23
- import { ___skipAPIOrgId, ___skipAPIurl, apiKey, baseUrl, configInfo, graphqlPort, mj_core_schema } from '../config.js';
25
+ import { ___skipAPIOrgId, ___skipAPIurl, ___skipLearningAPIurl, ___skipLearningCycleIntervalInMinutes, apiKey, baseUrl, configInfo, graphqlPort, mj_core_schema } from '../config.js';
24
26
  import { registerEnumType } from 'type-graphql';
25
27
  import { MJGlobal, CopyScalarsAndArrays } from '@memberjunction/global';
26
28
  import { sendPostRequest } from '../util.js';
@@ -79,9 +81,149 @@ AskSkipResultType = __decorate([
79
81
  ObjectType()
80
82
  ], AskSkipResultType);
81
83
  export { AskSkipResultType };
84
+ let ManualLearningCycleResultType = class ManualLearningCycleResultType {
85
+ Success;
86
+ Message;
87
+ };
88
+ __decorate([
89
+ Field(() => Boolean),
90
+ __metadata("design:type", Boolean)
91
+ ], ManualLearningCycleResultType.prototype, "Success", void 0);
92
+ __decorate([
93
+ Field(() => String),
94
+ __metadata("design:type", String)
95
+ ], ManualLearningCycleResultType.prototype, "Message", void 0);
96
+ ManualLearningCycleResultType = __decorate([
97
+ ObjectType()
98
+ ], ManualLearningCycleResultType);
99
+ export { ManualLearningCycleResultType };
100
+ let CycleDetailsType = class CycleDetailsType {
101
+ LearningCycleId;
102
+ StartTime;
103
+ RunningForMinutes;
104
+ };
105
+ __decorate([
106
+ Field(() => String),
107
+ __metadata("design:type", String)
108
+ ], CycleDetailsType.prototype, "LearningCycleId", void 0);
109
+ __decorate([
110
+ Field(() => String),
111
+ __metadata("design:type", String)
112
+ ], CycleDetailsType.prototype, "StartTime", void 0);
113
+ __decorate([
114
+ Field(() => Number),
115
+ __metadata("design:type", Number)
116
+ ], CycleDetailsType.prototype, "RunningForMinutes", void 0);
117
+ CycleDetailsType = __decorate([
118
+ ObjectType()
119
+ ], CycleDetailsType);
120
+ export { CycleDetailsType };
121
+ let RunningOrganizationType = class RunningOrganizationType {
122
+ OrganizationId;
123
+ LearningCycleId;
124
+ StartTime;
125
+ RunningForMinutes;
126
+ };
127
+ __decorate([
128
+ Field(() => String),
129
+ __metadata("design:type", String)
130
+ ], RunningOrganizationType.prototype, "OrganizationId", void 0);
131
+ __decorate([
132
+ Field(() => String),
133
+ __metadata("design:type", String)
134
+ ], RunningOrganizationType.prototype, "LearningCycleId", void 0);
135
+ __decorate([
136
+ Field(() => String),
137
+ __metadata("design:type", String)
138
+ ], RunningOrganizationType.prototype, "StartTime", void 0);
139
+ __decorate([
140
+ Field(() => Number),
141
+ __metadata("design:type", Number)
142
+ ], RunningOrganizationType.prototype, "RunningForMinutes", void 0);
143
+ RunningOrganizationType = __decorate([
144
+ ObjectType()
145
+ ], RunningOrganizationType);
146
+ export { RunningOrganizationType };
147
+ let LearningCycleStatusType = class LearningCycleStatusType {
148
+ IsSchedulerRunning;
149
+ LastRunTime;
150
+ RunningOrganizations;
151
+ };
152
+ __decorate([
153
+ Field(() => Boolean),
154
+ __metadata("design:type", Boolean)
155
+ ], LearningCycleStatusType.prototype, "IsSchedulerRunning", void 0);
156
+ __decorate([
157
+ Field(() => String, { nullable: true }),
158
+ __metadata("design:type", String)
159
+ ], LearningCycleStatusType.prototype, "LastRunTime", void 0);
160
+ __decorate([
161
+ Field(() => [RunningOrganizationType], { nullable: true }),
162
+ __metadata("design:type", Array)
163
+ ], LearningCycleStatusType.prototype, "RunningOrganizations", void 0);
164
+ LearningCycleStatusType = __decorate([
165
+ ObjectType()
166
+ ], LearningCycleStatusType);
167
+ export { LearningCycleStatusType };
168
+ let StopLearningCycleResultType = class StopLearningCycleResultType {
169
+ Success;
170
+ Message;
171
+ WasRunning;
172
+ CycleDetails;
173
+ };
174
+ __decorate([
175
+ Field(() => Boolean),
176
+ __metadata("design:type", Boolean)
177
+ ], StopLearningCycleResultType.prototype, "Success", void 0);
178
+ __decorate([
179
+ Field(() => String),
180
+ __metadata("design:type", String)
181
+ ], StopLearningCycleResultType.prototype, "Message", void 0);
182
+ __decorate([
183
+ Field(() => Boolean),
184
+ __metadata("design:type", Boolean)
185
+ ], StopLearningCycleResultType.prototype, "WasRunning", void 0);
186
+ __decorate([
187
+ Field(() => CycleDetailsType, { nullable: true }),
188
+ __metadata("design:type", CycleDetailsType)
189
+ ], StopLearningCycleResultType.prototype, "CycleDetails", void 0);
190
+ StopLearningCycleResultType = __decorate([
191
+ ObjectType()
192
+ ], StopLearningCycleResultType);
193
+ export { StopLearningCycleResultType };
82
194
  let AskSkipResolver = class AskSkipResolver {
83
195
  static { AskSkipResolver_1 = this; }
84
196
  static _defaultNewChatName = 'New Chat';
197
+ static {
198
+ try {
199
+ LogStatus('Initializing Skip AI Learning Cycle Scheduler');
200
+ const eventListener = MJGlobal.Instance.GetEventListener(true);
201
+ eventListener.subscribe(event => {
202
+ if (event.eventCode === MJ_SERVER_EVENT_CODE && event.args?.type === 'setupComplete') {
203
+ try {
204
+ const dataSources = event.args.dataSources;
205
+ if (dataSources && dataSources.length > 0) {
206
+ const scheduler = LearningCycleScheduler.Instance;
207
+ scheduler.setDataSources(dataSources);
208
+ const interval = ___skipLearningCycleIntervalInMinutes ?? 60;
209
+ scheduler.start(interval);
210
+ LogStatus(`📅 Skip AI Learning cycle scheduler started with ${interval} minute interval`);
211
+ }
212
+ else {
213
+ LogError('Cannot initialize Skip learning cycle scheduler: No data sources available');
214
+ }
215
+ }
216
+ catch (error) {
217
+ LogError(`Error initializing Skip learning cycle scheduler: ${error}`);
218
+ }
219
+ }
220
+ });
221
+ LogStatus('Skip AI Learning Cycle Scheduler initialization listener registered');
222
+ }
223
+ catch (error) {
224
+ LogError(`Failed to initialize Skip learning cycle scheduler: ${error}`);
225
+ }
226
+ }
85
227
  static _maxHistoricalMessages = 30;
86
228
  async ExecuteAskSkipRecordChat(UserQuestion, ConversationId, EntityName, compositeKey, { dataSource, userPayload }, pubSub) {
87
229
  const user = UserCache.Instance.Users.find((u) => u.Email.trim().toLowerCase() === userPayload.email.trim().toLowerCase());
@@ -92,7 +234,7 @@ let AskSkipResolver = class AskSkipResolver {
92
234
  messages = await this.LoadConversationDetailsIntoSkipMessages(dataSource, ConversationId, AskSkipResolver_1._maxHistoricalMessages);
93
235
  }
94
236
  const md = new Metadata();
95
- const { convoEntity, dataContextEntity, convoDetailEntity, dataContext } = await this.HandleSkipInitialObjectLoading(dataSource, ConversationId, UserQuestion, user, userPayload, md, null);
237
+ const { convoEntity, dataContextEntity, convoDetailEntity, dataContext } = await this.HandleSkipChatInitialObjectLoading(dataSource, ConversationId, UserQuestion, user, userPayload, md, null);
96
238
  if (!ConversationId || ConversationId.length === 0) {
97
239
  const dci = await md.GetEntityObject('Data Context Items', user);
98
240
  dci.DataContextID = dataContext.ID;
@@ -115,16 +257,105 @@ let AskSkipResolver = class AskSkipResolver {
115
257
  LogError(`Error saving ConversationEntity for record chat: ${EntityName} ${ck.Values()}`, undefined, convoEntity.LatestResult);
116
258
  }
117
259
  }
118
- const input = await this.buildSkipAPIRequest(messages, ConversationId, dataContext, 'chat_with_a_record', false, false, false, user, dataSource, false, false);
260
+ const input = await this.buildSkipChatAPIRequest(messages, ConversationId, dataContext, 'chat_with_a_record', false, false, false, false, user, dataSource, false, false);
119
261
  messages.push({
120
262
  content: UserQuestion,
121
263
  role: 'user',
122
264
  conversationDetailID: convoDetailEntity.ID,
123
265
  });
124
- return this.handleSimpleSkipPostRequest(input, convoEntity.ID, convoDetailEntity.ID, true, user);
266
+ return this.handleSimpleSkipChatPostRequest(input, convoEntity.ID, convoDetailEntity.ID, true, user);
267
+ }
268
+ async ExecuteAskSkipLearningCycle({ dataSource, userPayload }, ForceEntityRefresh) {
269
+ const startTime = new Date();
270
+ const user = UserCache.Instance.Users.find((u) => u.Email.trim().toLowerCase() === userPayload.email.trim().toLowerCase());
271
+ if (!user)
272
+ throw new Error(`User ${userPayload.email} not found in UserCache`);
273
+ await AIEngine.Instance.Config(false, user);
274
+ const organizationId = ___skipAPIOrgId;
275
+ const scheduler = LearningCycleScheduler.Instance;
276
+ const runningStatus = scheduler.isOrganizationRunningCycle(organizationId);
277
+ if (runningStatus.isRunning) {
278
+ LogStatus(`Learning cycle already in progress for organization ${organizationId}, started at ${runningStatus.startTime.toISOString()}`);
279
+ return {
280
+ success: false,
281
+ error: `Learning cycle already in progress for this organization (started ${Math.round(runningStatus.runningForMinutes)} minutes ago)`,
282
+ elapsedTime: 0,
283
+ noteChanges: [],
284
+ queryChanges: [],
285
+ requestChanges: []
286
+ };
287
+ }
288
+ LogStatus(`Starting learning cycle for AI agent Skip`);
289
+ const md = new Metadata();
290
+ const skipAgent = AIEngine.Instance.GetAgentByName('Skip');
291
+ if (!skipAgent) {
292
+ throw new Error("Skip agent not found in AIEngine");
293
+ }
294
+ const agentID = skipAgent.ID;
295
+ const lastCompleteLearningCycleDate = await this.GetLastCompleteLearningCycleDate(agentID, user);
296
+ const learningCycleEntity = await md.GetEntityObject('AI Agent Learning Cycles', user);
297
+ learningCycleEntity.NewRecord();
298
+ learningCycleEntity.AgentID = skipAgent.ID;
299
+ learningCycleEntity.Status = 'In-Progress';
300
+ learningCycleEntity.StartedAt = startTime;
301
+ if (!(await learningCycleEntity.Save())) {
302
+ throw new Error(`Failed to create learning cycle record: ${learningCycleEntity.LatestResult.Error}`);
303
+ }
304
+ const learningCycleId = learningCycleEntity.ID;
305
+ LogStatus(`Created new learning cycle with ID: ${learningCycleId}`);
306
+ scheduler.registerRunningCycle(organizationId, learningCycleId);
307
+ try {
308
+ LogStatus(`Building Skip Learning API request`);
309
+ const input = await this.buildSkipLearningAPIRequest(learningCycleId, lastCompleteLearningCycleDate, true, true, true, true, dataSource, user, ForceEntityRefresh || false);
310
+ const response = await this.handleSimpleSkipLearningPostRequest(input, user, learningCycleId, agentID);
311
+ const endTime = new Date();
312
+ const elapsedTimeMs = endTime.getTime() - startTime.getTime();
313
+ LogStatus(`Learning cycle finished with status: ${response.success ? 'Success' : 'Failed'} in ${elapsedTimeMs / 1000} seconds`);
314
+ learningCycleEntity.Status = response.success ? 'Complete' : 'Failed';
315
+ learningCycleEntity.EndedAt = endTime;
316
+ if (!(await learningCycleEntity.Save())) {
317
+ LogError(`Failed to update learning cycle record: ${learningCycleEntity.LatestResult.Error}`);
318
+ }
319
+ scheduler.unregisterRunningCycle(organizationId);
320
+ return response;
321
+ }
322
+ catch (error) {
323
+ learningCycleEntity.Status = 'Failed';
324
+ learningCycleEntity.EndedAt = new Date();
325
+ try {
326
+ await learningCycleEntity.Save();
327
+ }
328
+ catch (saveError) {
329
+ LogError(`Failed to update learning cycle record after error: ${saveError}`);
330
+ }
331
+ scheduler.unregisterRunningCycle(organizationId);
332
+ throw error;
333
+ }
334
+ }
335
+ async handleSimpleSkipLearningPostRequest(input, user, learningCycleId, agentID) {
336
+ LogStatus(` >>> HandleSimpleSkipLearningPostRequest Sending request to Skip API: ${___skipLearningAPIurl}`);
337
+ const response = await sendPostRequest(___skipLearningAPIurl, input, true, null);
338
+ if (response && response.length > 0) {
339
+ const apiResponse = response[response.length - 1].value;
340
+ LogStatus(` Skip API response: ${apiResponse.success}`);
341
+ if (apiResponse.noteChanges && apiResponse.noteChanges.length > 0) {
342
+ await this.processLearningCycleNoteChanges(apiResponse.noteChanges, agentID, user);
343
+ }
344
+ return apiResponse;
345
+ }
346
+ else {
347
+ return {
348
+ success: false,
349
+ error: 'Error',
350
+ elapsedTime: 0,
351
+ noteChanges: [],
352
+ queryChanges: [],
353
+ requestChanges: [],
354
+ };
355
+ }
125
356
  }
126
- async handleSimpleSkipPostRequest(input, conversationID = '', UserMessageConversationDetailId = '', createAIMessageConversationDetail = false, user = null) {
127
- LogStatus(` >>> HandleSimpleSkipPostRequest Sending request to Skip API: ${___skipAPIurl}`);
357
+ async handleSimpleSkipChatPostRequest(input, conversationID = '', UserMessageConversationDetailId = '', createAIMessageConversationDetail = false, user = null) {
358
+ LogStatus(` >>> HandleSimpleSkipChatPostRequest Sending request to Skip API: ${___skipAPIurl}`);
128
359
  const response = await sendPostRequest(___skipAPIurl, input, true, null);
129
360
  if (response && response.length > 0) {
130
361
  const apiResponse = response[response.length - 1].value;
@@ -154,6 +385,85 @@ let AskSkipResolver = class AskSkipResolver {
154
385
  };
155
386
  }
156
387
  }
388
+ async processLearningCycleNoteChanges(noteChanges, agentID, user) {
389
+ const md = new Metadata();
390
+ const validNoteChanges = noteChanges.filter(change => {
391
+ if (change.note.agentNoteType === "Human") {
392
+ LogStatus(`WARNING: Ignoring ${change.changeType} operation on Human note with ID ${change.note.id}. Human notes cannot be modified by the
393
+ learning cycle.`);
394
+ return false;
395
+ }
396
+ return true;
397
+ });
398
+ await Promise.all(validNoteChanges.map(async (change) => {
399
+ try {
400
+ if (change.changeType === 'add' || change.changeType === 'update') {
401
+ await this.processAddOrUpdateSkipNote(change, agentID, user);
402
+ }
403
+ else if (change.changeType === 'delete') {
404
+ await this.processDeleteSkipNote(change, user);
405
+ }
406
+ }
407
+ catch (e) {
408
+ LogError(`Error processing note change: ${e}`);
409
+ }
410
+ }));
411
+ }
412
+ async processAddOrUpdateSkipNote(change, agentID, user) {
413
+ try {
414
+ const md = new Metadata();
415
+ const noteEntity = await md.GetEntityObject('AI Agent Notes', user);
416
+ if (change.changeType === 'update') {
417
+ const loadResult = await noteEntity.Load(change.note.id);
418
+ if (!loadResult) {
419
+ LogError(`Could not load note with ID ${change.note.id}`);
420
+ return false;
421
+ }
422
+ }
423
+ else {
424
+ if (change.note.agentNoteType === "Human") {
425
+ LogStatus(`WARNING: Cannot create a new Human note with the learning cycle. Operation ignored.`);
426
+ return false;
427
+ }
428
+ noteEntity.NewRecord();
429
+ noteEntity.AgentID = agentID;
430
+ }
431
+ noteEntity.AgentNoteTypeID = this.getAgentNoteTypeIDByName('AI');
432
+ noteEntity.Note = change.note.note;
433
+ noteEntity.Type = change.note.type;
434
+ if (change.note.type === 'User') {
435
+ noteEntity.UserID = change.note.userId;
436
+ }
437
+ if (!(await noteEntity.Save())) {
438
+ LogError(`Error saving AI Agent Note: ${noteEntity.LatestResult.Error}`);
439
+ return false;
440
+ }
441
+ return true;
442
+ }
443
+ catch (e) {
444
+ LogError(`Error processing note change: ${e}`);
445
+ return false;
446
+ }
447
+ }
448
+ async processDeleteSkipNote(change, user) {
449
+ const md = new Metadata();
450
+ const noteEntity = await md.GetEntityObject('AI Agent Notes', user);
451
+ const loadResult = await noteEntity.Load(change.note.id);
452
+ if (!loadResult) {
453
+ LogError(`Could not load note with ID ${change.note.id} for deletion`);
454
+ return false;
455
+ }
456
+ if (change.note.agentNoteType === "Human") {
457
+ LogStatus(`WARNING: Ignoring delete operation on Human note with ID ${change.note.id}. Human notes cannot be deleted by the learning
458
+ cycle.`);
459
+ return false;
460
+ }
461
+ if (!(await noteEntity.Delete())) {
462
+ LogError(`Error deleting AI Agent Note: ${noteEntity.LatestResult.Error}`);
463
+ return false;
464
+ }
465
+ return true;
466
+ }
157
467
  async CreateAIMessageConversationDetail(apiResponse, conversationID, user) {
158
468
  const md = new Metadata();
159
469
  const convoDetailEntityAI = await md.GetEntityObject('Conversation Details', user);
@@ -172,37 +482,151 @@ let AskSkipResolver = class AskSkipResolver {
172
482
  return '';
173
483
  }
174
484
  }
175
- async buildSkipAPIRequest(messages, conversationId, dataContext, requestPhase, includeEntities, includeQueries, includeNotes, contextUser, dataSource, forceEntitiesRefresh = false, includeCallBackKeyAndAccessToken = false) {
485
+ async buildBaseSkipRequest(contextUser, dataSource, includeEntities, includeQueries, includeNotes, includeRequests, forceEntitiesRefresh = false, includeCallBackKeyAndAccessToken = false, additionalTokenInfo = {}) {
176
486
  const entities = includeEntities ? await this.BuildSkipEntities(dataSource, forceEntitiesRefresh) : [];
177
487
  const queries = includeQueries ? this.BuildSkipQueries() : [];
178
488
  const { notes, noteTypes } = includeNotes ? await this.BuildSkipAgentNotes(contextUser) : { notes: [], noteTypes: [] };
489
+ const requests = includeRequests ? await this.BuildSkipRequests(contextUser) : [];
179
490
  let accessToken;
180
491
  if (includeCallBackKeyAndAccessToken) {
181
- accessToken = registerAccessToken(undefined, 1000 * 60 * 10, {
492
+ const tokenInfo = {
182
493
  type: 'skip_api_request',
183
494
  userEmail: contextUser.Email,
184
495
  userName: contextUser.Name,
185
496
  userID: contextUser.ID,
186
- conversationId: conversationId,
187
- requestPhase: requestPhase,
188
- });
497
+ ...additionalTokenInfo
498
+ };
499
+ accessToken = registerAccessToken(undefined, 1000 * 60 * 10, tokenInfo);
189
500
  }
190
- const input = {
191
- apiKeys: this.buildSkipAPIKeys(),
501
+ return {
502
+ entities,
503
+ queries,
504
+ notes,
505
+ noteTypes,
506
+ requests,
507
+ accessToken,
508
+ organizationId: ___skipAPIOrgId,
192
509
  organizationInfo: configInfo?.askSkip?.organizationInfo,
193
- messages: messages,
194
- conversationID: conversationId.toString(),
195
- dataContext: CopyScalarsAndArrays(dataContext),
196
- organizationID: ___skipAPIOrgId,
197
- requestPhase: requestPhase,
198
- entities: entities,
199
- queries: queries,
200
- notes: notes,
201
- noteTypes: noteTypes,
510
+ apiKeys: this.buildSkipAPIKeys(),
202
511
  callingServerURL: accessToken ? `${baseUrl}:${graphqlPort}` : undefined,
203
512
  callingServerAPIKey: accessToken ? apiKey : undefined,
204
513
  callingServerAccessToken: accessToken ? accessToken.Token : undefined
205
514
  };
515
+ }
516
+ async buildSkipLearningAPIRequest(learningCycleId, lastLearningCycleDate, includeEntities, includeQueries, includeNotes, includeRequests, dataSource, contextUser, forceEntitiesRefresh = false, includeCallBackKeyAndAccessToken = false) {
517
+ const baseRequest = await this.buildBaseSkipRequest(contextUser, dataSource, includeEntities, includeQueries, includeNotes, includeRequests, forceEntitiesRefresh, includeCallBackKeyAndAccessToken);
518
+ const newConversations = await this.BuildSkipLearningCycleNewConversations(lastLearningCycleDate, dataSource, contextUser);
519
+ const input = {
520
+ organizationId: baseRequest.organizationId,
521
+ organizationInfo: baseRequest.organizationInfo,
522
+ learningCycleId,
523
+ lastLearningCycleDate,
524
+ newConversations,
525
+ entities: baseRequest.entities,
526
+ queries: baseRequest.queries,
527
+ notes: baseRequest.notes,
528
+ noteTypes: baseRequest.noteTypes,
529
+ requests: baseRequest.requests,
530
+ apiKeys: baseRequest.apiKeys
531
+ };
532
+ return input;
533
+ }
534
+ async BuildSkipLearningCycleNewConversations(lastLearningCycleDate, dataSource, contextUser) {
535
+ try {
536
+ const rv = new RunView();
537
+ const conversationsSinceLastLearningCycle = await rv.RunView({
538
+ EntityName: 'Conversations',
539
+ ExtraFilter: `ID IN (SELECT ConversationID FROM __mj.vwConversationDetails WHERE __mj_UpdatedAt >= '${lastLearningCycleDate.toISOString()}')`,
540
+ ResultType: 'entity_object',
541
+ }, contextUser);
542
+ if (!conversationsSinceLastLearningCycle.Success || conversationsSinceLastLearningCycle.Results.length === 0) {
543
+ return [];
544
+ }
545
+ return await Promise.all(conversationsSinceLastLearningCycle.Results.map(async (c) => {
546
+ return {
547
+ id: c.ID,
548
+ name: c.Name,
549
+ userId: c.UserID,
550
+ user: c.User,
551
+ description: c.Description,
552
+ messages: await this.LoadConversationDetailsIntoSkipMessages(dataSource, c.ID),
553
+ createdAt: c.__mj_CreatedAt,
554
+ updatedAt: c.__mj_UpdatedAt
555
+ };
556
+ }));
557
+ }
558
+ catch (e) {
559
+ LogError(`Error loading conversations since last learning cycle: ${e}`);
560
+ return [];
561
+ }
562
+ }
563
+ async BuildSkipRequests(contextUser) {
564
+ try {
565
+ const md = new Metadata();
566
+ const requestEntity = await md.GetEntityObject('AI Agent Requests', contextUser);
567
+ const allRequests = await requestEntity.GetAll();
568
+ const requests = allRequests.map((r) => {
569
+ return {
570
+ id: r.ID,
571
+ agentId: r.AIAgentID,
572
+ agnet: r.AIAgent,
573
+ requestedAt: r.RequestedAt,
574
+ requestForUserId: r.RequestedForUserID,
575
+ requestForUser: r.RequestedForUser,
576
+ status: r.Status,
577
+ request: r.Request,
578
+ response: r.Response,
579
+ responseByUserId: r.ResponseByUserID,
580
+ responseByUser: r.ResponseByUser,
581
+ respondedAt: r.RespondedAt,
582
+ comments: r.Comments,
583
+ createdAt: r.__mj_CreatedAt,
584
+ updatedAt: r.__mj_UpdatedAt,
585
+ };
586
+ });
587
+ return requests;
588
+ }
589
+ catch (e) {
590
+ LogError(`Error loading requests: ${e}`);
591
+ return [];
592
+ }
593
+ }
594
+ async GetLastCompleteLearningCycleDate(agentID, user) {
595
+ const md = new Metadata();
596
+ const rv = new RunView();
597
+ const lastLearningCycleRV = await rv.RunView({
598
+ EntityName: 'AI Agent Learning Cycles',
599
+ ExtraFilter: `AgentID = '${agentID}' AND Status = 'Complete'`,
600
+ ResultType: 'entity_object',
601
+ OrderBy: 'StartedAt DESC',
602
+ MaxRows: 1,
603
+ }, user);
604
+ const lastLearningCycle = lastLearningCycleRV.Results[0];
605
+ if (lastLearningCycle) {
606
+ return lastLearningCycle.StartedAt;
607
+ }
608
+ else {
609
+ return new Date(0);
610
+ }
611
+ }
612
+ async buildSkipChatAPIRequest(messages, conversationId, dataContext, requestPhase, includeEntities, includeQueries, includeNotes, includeRequests, contextUser, dataSource, forceEntitiesRefresh = false, includeCallBackKeyAndAccessToken = false) {
613
+ const additionalTokenInfo = {
614
+ conversationId,
615
+ requestPhase,
616
+ };
617
+ const baseRequest = await this.buildBaseSkipRequest(contextUser, dataSource, includeEntities, includeQueries, includeNotes, includeRequests, forceEntitiesRefresh, includeCallBackKeyAndAccessToken, additionalTokenInfo);
618
+ const input = {
619
+ messages,
620
+ conversationID: conversationId.toString(),
621
+ dataContext: CopyScalarsAndArrays(dataContext),
622
+ organizationID: baseRequest.organizationId,
623
+ requestPhase,
624
+ entities: baseRequest.entities,
625
+ queries: baseRequest.queries,
626
+ notes: baseRequest.notes,
627
+ noteTypes: baseRequest.noteTypes,
628
+ apiKeys: baseRequest.apiKeys,
629
+ };
206
630
  return input;
207
631
  }
208
632
  async ExecuteAskSkipRunScript({ dataSource, userPayload }, pubSub, DataContextId, ScriptText) {
@@ -211,9 +635,9 @@ let AskSkipResolver = class AskSkipResolver {
211
635
  throw new Error(`User ${userPayload.email} not found in UserCache`);
212
636
  const dataContext = new DataContext();
213
637
  await dataContext.Load(DataContextId, dataSource, true, false, 0, user);
214
- const input = await this.buildSkipAPIRequest([], '', dataContext, 'run_existing_script', false, false, false, user, dataSource, false, false);
638
+ const input = await this.buildSkipChatAPIRequest([], '', dataContext, 'run_existing_script', false, false, false, false, user, dataSource, false, false);
215
639
  input.scriptText = ScriptText;
216
- return this.handleSimpleSkipPostRequest(input);
640
+ return this.handleSimpleSkipChatPostRequest(input);
217
641
  }
218
642
  buildSkipAPIKeys() {
219
643
  return [
@@ -244,11 +668,11 @@ let AskSkipResolver = class AskSkipResolver {
244
668
  const user = UserCache.Instance.Users.find((u) => u.Email.trim().toLowerCase() === userPayload.email.trim().toLowerCase());
245
669
  if (!user)
246
670
  throw new Error(`User ${userPayload.email} not found in UserCache`);
247
- const { convoEntity, dataContextEntity, convoDetailEntity, dataContext } = await this.HandleSkipInitialObjectLoading(dataSource, ConversationId, UserQuestion, user, userPayload, md, DataContextId);
671
+ const { convoEntity, dataContextEntity, convoDetailEntity, dataContext } = await this.HandleSkipChatInitialObjectLoading(dataSource, ConversationId, UserQuestion, user, userPayload, md, DataContextId);
248
672
  const messages = await this.LoadConversationDetailsIntoSkipMessages(dataSource, convoEntity.ID, AskSkipResolver_1._maxHistoricalMessages);
249
673
  const conversationDetailCount = 1;
250
- const input = await this.buildSkipAPIRequest(messages, ConversationId, dataContext, 'initial_request', true, true, true, user, dataSource, ForceEntityRefresh === undefined ? false : ForceEntityRefresh, true);
251
- return this.HandleSkipRequest(input, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount);
674
+ const input = await this.buildSkipChatAPIRequest(messages, ConversationId, dataContext, 'initial_request', true, true, true, false, user, dataSource, ForceEntityRefresh === undefined ? false : ForceEntityRefresh, true);
675
+ return this.HandleSkipChatRequest(input, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount);
252
676
  }
253
677
  BuildSkipQueries(status = 'Approved') {
254
678
  const md = new Metadata();
@@ -565,7 +989,7 @@ let AskSkipResolver = class AskSkipResolver {
565
989
  return null;
566
990
  }
567
991
  }
568
- async HandleSkipInitialObjectLoading(dataSource, ConversationId, UserQuestion, user, userPayload, md, DataContextId) {
992
+ async HandleSkipChatInitialObjectLoading(dataSource, ConversationId, UserQuestion, user, userPayload, md, DataContextId) {
569
993
  const convoEntity = await md.GetEntityObject('Conversations', user);
570
994
  let dataContextEntity;
571
995
  if (!ConversationId || ConversationId.length === 0) {
@@ -747,7 +1171,7 @@ let AskSkipResolver = class AskSkipResolver {
747
1171
  return 'user';
748
1172
  }
749
1173
  }
750
- async HandleSkipRequest(input, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount) {
1174
+ async HandleSkipChatRequest(input, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount) {
751
1175
  LogStatus(` >>> HandleSkipRequest: Sending request to Skip API: ${___skipAPIurl}`);
752
1176
  if (conversationDetailCount > 10) {
753
1177
  pubSub.publish(PUSH_STATUS_UPDATES_TOPIC, {
@@ -999,7 +1423,7 @@ let AskSkipResolver = class AskSkipResolver {
999
1423
  apiRequest.requestPhase = 'data_gathering_response';
1000
1424
  }
1001
1425
  conversationDetailCount++;
1002
- return this.HandleSkipRequest(apiRequest, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount);
1426
+ return this.HandleSkipChatRequest(apiRequest, UserQuestion, user, dataSource, ConversationId, userPayload, pubSub, md, convoEntity, convoDetailEntity, dataContext, dataContextEntity, conversationDetailCount);
1003
1427
  }
1004
1428
  catch (e) {
1005
1429
  LogError(e);
@@ -1066,6 +1490,16 @@ let AskSkipResolver = class AskSkipResolver {
1066
1490
  AIMessageConversationDetailID: convoDetailEntityAI.ID,
1067
1491
  };
1068
1492
  }
1493
+ getAgentNoteTypeIDByName(name) {
1494
+ const noteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === name.trim().toLowerCase())?.ID;
1495
+ if (noteTypeID) {
1496
+ return noteTypeID;
1497
+ }
1498
+ else {
1499
+ const AINoteTypeID = AIEngine.Instance.AgentNoteTypes.find(nt => nt.Name.trim().toLowerCase() === 'AI')?.ID;
1500
+ return AINoteTypeID;
1501
+ }
1502
+ }
1069
1503
  async getViewData(ViewId, user) {
1070
1504
  const rv = new RunView();
1071
1505
  const result = await rv.RunView({ ViewID: ViewId, IgnoreMaxRows: true }, user);
@@ -1074,6 +1508,93 @@ let AskSkipResolver = class AskSkipResolver {
1074
1508
  else
1075
1509
  throw new Error(`Error running view ${ViewId}`);
1076
1510
  }
1511
+ async ManuallyExecuteSkipLearningCycle(OrganizationId) {
1512
+ try {
1513
+ LogStatus('Manual execution of Skip learning cycle requested via API');
1514
+ const orgId = OrganizationId || ___skipAPIOrgId;
1515
+ const result = await LearningCycleScheduler.Instance.manuallyExecuteLearningCycle(orgId);
1516
+ return {
1517
+ Success: result,
1518
+ Message: result
1519
+ ? `Learning cycle was successfully executed manually for organization ${orgId}`
1520
+ : `Learning cycle execution failed for organization ${orgId}. Check server logs for details.`
1521
+ };
1522
+ }
1523
+ catch (e) {
1524
+ LogError(`Error in ManuallyExecuteSkipLearningCycle: ${e}`);
1525
+ return {
1526
+ Success: false,
1527
+ Message: `Error executing learning cycle: ${e}`
1528
+ };
1529
+ }
1530
+ }
1531
+ async GetLearningCycleStatus() {
1532
+ try {
1533
+ const status = LearningCycleScheduler.Instance.getStatus();
1534
+ return {
1535
+ IsSchedulerRunning: status.isSchedulerRunning,
1536
+ LastRunTime: status.lastRunTime ? status.lastRunTime.toISOString() : null,
1537
+ RunningOrganizations: status.runningOrganizations ? status.runningOrganizations.map(org => ({
1538
+ OrganizationId: org.organizationId,
1539
+ LearningCycleId: org.learningCycleId,
1540
+ StartTime: org.startTime.toISOString(),
1541
+ RunningForMinutes: org.runningForMinutes
1542
+ })) : []
1543
+ };
1544
+ }
1545
+ catch (e) {
1546
+ LogError(`Error in GetLearningCycleStatus: ${e}`);
1547
+ return {
1548
+ IsSchedulerRunning: false,
1549
+ LastRunTime: null,
1550
+ RunningOrganizations: []
1551
+ };
1552
+ }
1553
+ }
1554
+ async IsOrganizationRunningLearningCycle(OrganizationId) {
1555
+ try {
1556
+ const orgId = OrganizationId || ___skipAPIOrgId;
1557
+ const status = LearningCycleScheduler.Instance.isOrganizationRunningCycle(orgId);
1558
+ if (!status.isRunning) {
1559
+ return null;
1560
+ }
1561
+ return {
1562
+ OrganizationId: orgId,
1563
+ LearningCycleId: status.learningCycleId,
1564
+ StartTime: status.startTime.toISOString(),
1565
+ RunningForMinutes: status.runningForMinutes
1566
+ };
1567
+ }
1568
+ catch (e) {
1569
+ LogError(`Error in IsOrganizationRunningLearningCycle: ${e}`);
1570
+ return null;
1571
+ }
1572
+ }
1573
+ async StopLearningCycleForOrganization(OrganizationId) {
1574
+ try {
1575
+ const orgId = OrganizationId || ___skipAPIOrgId;
1576
+ const result = LearningCycleScheduler.Instance.stopLearningCycleForOrganization(orgId);
1577
+ return {
1578
+ Success: result.success,
1579
+ Message: result.message,
1580
+ WasRunning: result.wasRunning,
1581
+ CycleDetails: result.cycleDetails ? {
1582
+ LearningCycleId: result.cycleDetails.learningCycleId,
1583
+ StartTime: result.cycleDetails.startTime.toISOString(),
1584
+ RunningForMinutes: result.cycleDetails.runningForMinutes
1585
+ } : null
1586
+ };
1587
+ }
1588
+ catch (e) {
1589
+ LogError(`Error in StopLearningCycleForOrganization: ${e}`);
1590
+ return {
1591
+ Success: false,
1592
+ Message: `Error stopping learning cycle: ${e}`,
1593
+ WasRunning: false,
1594
+ CycleDetails: null
1595
+ };
1596
+ }
1597
+ }
1077
1598
  };
1078
1599
  __decorate([
1079
1600
  Query(() => AskSkipResultType),
@@ -1087,6 +1608,14 @@ __decorate([
1087
1608
  __metadata("design:paramtypes", [String, String, String, CompositeKeyInputType, Object, PubSubEngine]),
1088
1609
  __metadata("design:returntype", Promise)
1089
1610
  ], AskSkipResolver.prototype, "ExecuteAskSkipRecordChat", null);
1611
+ __decorate([
1612
+ Mutation(() => AskSkipResultType),
1613
+ __param(0, Ctx()),
1614
+ __param(1, Arg('ForceEntityRefresh', () => Boolean, { nullable: true })),
1615
+ __metadata("design:type", Function),
1616
+ __metadata("design:paramtypes", [Object, Boolean]),
1617
+ __metadata("design:returntype", Promise)
1618
+ ], AskSkipResolver.prototype, "ExecuteAskSkipLearningCycle", null);
1090
1619
  __decorate([
1091
1620
  Query(() => AskSkipResultType),
1092
1621
  __param(0, Ctx()),
@@ -1109,6 +1638,33 @@ __decorate([
1109
1638
  __metadata("design:paramtypes", [String, String, Object, PubSubEngine, String, Boolean]),
1110
1639
  __metadata("design:returntype", Promise)
1111
1640
  ], AskSkipResolver.prototype, "ExecuteAskSkipAnalysisQuery", null);
1641
+ __decorate([
1642
+ Mutation(() => ManualLearningCycleResultType),
1643
+ __param(0, Arg('OrganizationId', () => String, { nullable: true })),
1644
+ __metadata("design:type", Function),
1645
+ __metadata("design:paramtypes", [String]),
1646
+ __metadata("design:returntype", Promise)
1647
+ ], AskSkipResolver.prototype, "ManuallyExecuteSkipLearningCycle", null);
1648
+ __decorate([
1649
+ Query(() => LearningCycleStatusType),
1650
+ __metadata("design:type", Function),
1651
+ __metadata("design:paramtypes", []),
1652
+ __metadata("design:returntype", Promise)
1653
+ ], AskSkipResolver.prototype, "GetLearningCycleStatus", null);
1654
+ __decorate([
1655
+ Query(() => RunningOrganizationType, { nullable: true }),
1656
+ __param(0, Arg('OrganizationId', () => String)),
1657
+ __metadata("design:type", Function),
1658
+ __metadata("design:paramtypes", [String]),
1659
+ __metadata("design:returntype", Promise)
1660
+ ], AskSkipResolver.prototype, "IsOrganizationRunningLearningCycle", null);
1661
+ __decorate([
1662
+ Mutation(() => StopLearningCycleResultType),
1663
+ __param(0, Arg('OrganizationId', () => String)),
1664
+ __metadata("design:type", Function),
1665
+ __metadata("design:paramtypes", [String]),
1666
+ __metadata("design:returntype", Promise)
1667
+ ], AskSkipResolver.prototype, "StopLearningCycleForOrganization", null);
1112
1668
  AskSkipResolver = AskSkipResolver_1 = __decorate([
1113
1669
  Resolver(AskSkipResultType)
1114
1670
  ], AskSkipResolver);