@peopl-health/nexus 3.2.0 → 3.2.2

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.
@@ -46,11 +46,12 @@ class NexusMessaging {
46
46
  this.activeRequests = new Map(); // Track active AI requests per chat
47
47
  this.abandonedRuns = new Set(); // Track runs that should be ignored
48
48
  this.batchingConfig = {
49
- enabled: config.messageBatching?.enabled ?? true, // Enabled by default with check-after
50
- abortOnNewMessage: config.messageBatching?.abortOnNewMessage ?? true, // Abort ongoing AI calls when new messages arrive
51
- immediateRestart: config.messageBatching?.immediateRestart ?? true, // Start new processing immediately without waiting
52
- batchWindowMs: config.messageBatching?.batchWindowMs ?? 2000, // Wait up to 2s for message bursts
53
- maxBatchWait: config.messageBatching?.maxBatchWait ?? 5000 // Maximum time to wait for batching
49
+ enabled: config.messageBatching?.enabled ?? true,
50
+ abortOnNewMessage: config.messageBatching?.abortOnNewMessage ?? true,
51
+ immediateRestart: config.messageBatching?.immediateRestart ?? true,
52
+ batchWindowMs: config.messageBatching?.batchWindowMs ?? 2000,
53
+ maxBatchWait: config.messageBatching?.maxBatchWait ?? 5000,
54
+ typingIndicator: config.messageBatching?.typingIndicator ?? false
54
55
  };
55
56
  }
56
57
 
@@ -695,7 +696,9 @@ class NexusMessaging {
695
696
  async _processWithLock(chatId, existingTypingInterval = null) {
696
697
  this.processingLocks.set(chatId, true);
697
698
  let typingInterval = existingTypingInterval;
698
- let runId = null;
699
+
700
+ const runId = `run_${Date.now()}_${Math.random().toString(36).substring(7)}`;
701
+ this.activeRequests.set(chatId, runId);
699
702
 
700
703
  try {
701
704
  if (!typingInterval) {
@@ -707,6 +710,12 @@ class NexusMessaging {
707
710
  let lastCount = messageCount;
708
711
 
709
712
  while (Date.now() - startTime < this.batchingConfig.batchWindowMs) {
713
+ if (this.abandonedRuns.has(runId)) {
714
+ logger.info(`[Batching] Run ${runId} abandoned during batching for ${chatId}`);
715
+ this.abandonedRuns.delete(runId);
716
+ return;
717
+ }
718
+
710
719
  await new Promise(resolve => setTimeout(resolve, 500));
711
720
  const newCount = await this._getUnprocessedMessageCount(chatId);
712
721
 
@@ -721,11 +730,14 @@ class NexusMessaging {
721
730
  }
722
731
  }
723
732
 
733
+ if (this.abandonedRuns.has(runId)) {
734
+ logger.info(`[CheckAfter] Run ${runId} abandoned before AI call for ${chatId}`);
735
+ this.abandonedRuns.delete(runId);
736
+ return;
737
+ }
738
+
724
739
  logger.info(`[CheckAfter] Processing ${lastCount} messages for ${chatId} after batching`);
725
740
 
726
- runId = `run_${Date.now()}_${Math.random().toString(36).substring(7)}`;
727
- this.activeRequests.set(chatId, runId);
728
-
729
741
  const result = await this._processMessages(chatId, () => replyAssistant(chatId, null, null, { runId }));
730
742
 
731
743
  if (this.abandonedRuns.has(runId)) {
@@ -814,7 +826,7 @@ class NexusMessaging {
814
826
  * Start typing indicator refresh interval
815
827
  */
816
828
  async _startTypingRefresh(chatId) {
817
- if (!this.provider || typeof this.provider.sendTypingIndicator !== 'function') {
829
+ if (!this.batchingConfig.typingIndicator || !this.provider || typeof this.provider.sendTypingIndicator !== 'function') {
818
830
  return null;
819
831
  }
820
832
 
@@ -16,6 +16,7 @@ function getCurRow(baseID, code) {
16
16
  const runAssistantAndWait = async ({
17
17
  thread,
18
18
  assistant,
19
+ message = null,
19
20
  runConfig = {}
20
21
  }) => {
21
22
  if (!thread || !thread.getConversationId()) {
@@ -34,6 +35,7 @@ const runAssistantAndWait = async ({
34
35
  async (currentThread = thread) => {
35
36
  return await provider.executeRun({
36
37
  thread: currentThread,
38
+ message,
37
39
  assistant,
38
40
  tools,
39
41
  config,
@@ -58,7 +60,7 @@ const runAssistantWithRetries = async (thread, assistant, runConfig, patientRepl
58
60
  'thread.id': thread.getConversationId(),
59
61
  'assistant.id': thread.getAssistantId()
60
62
  })
61
- )({ thread, assistant, runConfig });
63
+ )({ thread, assistant, runConfig, message: patientReply });
62
64
 
63
65
  const predictionTimeMs = Date.now() - startTime;
64
66
 
@@ -11,26 +11,26 @@ class DefaultMemoryManager extends MemoryManager {
11
11
  this.maxHistoricalMessages = parseInt(process.env.MAX_HISTORICAL_MESSAGES || '50', 10);
12
12
  }
13
13
 
14
- async buildContext({ thread, config = {} }) {
14
+ async buildContext({ thread, message = null, config = {} }) {
15
15
  this._logActivity('Building context', { threadCode: thread.code });
16
16
 
17
17
  try {
18
- const allMessages = await getLastNMessages(thread.code, this.maxHistoricalMessages);
19
- const additionalMessages = config.additionalMessages || [];
18
+ const beforeCheckpoint = message ? message.createdAt : null;
19
+ const allMessages = await getLastNMessages(thread.code, this.maxHistoricalMessages, beforeCheckpoint);
20
20
 
21
21
  if (!allMessages?.length) {
22
- return additionalMessages;
22
+ return [];
23
23
  }
24
24
 
25
25
  const messageContext = allMessages.reverse().flatMap(msg => {
26
26
  const formattedContents = formatMessage(msg);
27
27
  return formattedContents.map(content => ({
28
- role: msg.origin === 'patient' ? 'user' : 'assistant',
28
+ role: msg.origin === 'instruction' ? 'developer' : msg.origin === 'patient' ? 'user' : 'assistant',
29
29
  content: content || msg.body || msg.content || ''
30
30
  }));
31
31
  }).filter(msg => msg.content);
32
32
 
33
- return [...additionalMessages, ...messageContext];
33
+ return messageContext;
34
34
  } catch (error) {
35
35
  logger.error('[DefaultMemoryManager] Context building failed', {
36
36
  threadCode: thread.code,
@@ -173,7 +173,7 @@ class OpenAIResponsesProvider {
173
173
  /**
174
174
  * Main entry point for running assistant
175
175
  */
176
- async executeRun({ thread, assistant, tools = [], config = {} }) {
176
+ async executeRun({ thread, assistant, message = null, tools = [], config = {} }) {
177
177
  const { conversationId, assistantId } = this._normalizeThread(thread);
178
178
  const promptVersion = thread?.version || null;
179
179
 
@@ -187,6 +187,7 @@ class OpenAIResponsesProvider {
187
187
  // Delegate context building to conversation manager
188
188
  const context = await this.conversationManager.buildContext({
189
189
  thread,
190
+ message,
190
191
  config: {
191
192
  ...config,
192
193
  threadId: conversationId,
@@ -112,14 +112,8 @@ const addInstructionCore = async (code, instruction, role = 'system') => {
112
112
 
113
113
  try {
114
114
  const assistant = getAssistantById(thread.getAssistantId(), thread);
115
- const runResult = await runAssistantWithRetries(thread, assistant, {
116
- additionalInstructions: instruction,
117
- additionalMessages: [
118
- { role: role, content: instruction }
119
- ]
120
- });
121
115
 
122
- // Save instruction message to database for frontend visibility
116
+ // Save instruction message to database
123
117
  try {
124
118
  const message_id = `instruction_${Date.now()}_${Math.random().toString(36).substring(7)}`;
125
119
  await insertMessage({
@@ -137,6 +131,13 @@ const addInstructionCore = async (code, instruction, role = 'system') => {
137
131
  logger.error('[addInstructionCore] Error saving instruction message', { err });
138
132
  }
139
133
 
134
+ const runResult = await runAssistantWithRetries(thread, assistant, {
135
+ additionalInstructions: instruction,
136
+ additionalMessages: [
137
+ { role: role, content: instruction }
138
+ ]
139
+ });
140
+
140
141
  logger.info('[addInstructionCore] Run response', { output: runResult?.output });
141
142
  return runResult?.output || null;
142
143
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "3.2.0",
3
+ "version": "3.2.2",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",