@peopl-health/nexus 3.0.1 → 3.0.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.
@@ -66,13 +66,12 @@ class GeneralAssistant extends BaseAssistant {
66
66
  }
67
67
 
68
68
  async function startServer() {
69
- // Initialize Nexus with all services and batching enabled
69
+ // Initialize Nexus with check-after processing (immediate response, checks for new messages after)
70
70
  const nexus = new Nexus({
71
71
  messaging: {
72
72
  messageBatching: {
73
- enabled: true,
74
- baseWaitTime: 10000, // 10 seconds base wait (shorter for testing)
75
- randomVariation: 5000 // 0-5 seconds random variation
73
+ enabled: true, // Enable check-after processing
74
+ checkDelayMs: 100 // Delay before checking for new messages (ms)
76
75
  }
77
76
  }
78
77
  });
@@ -41,11 +41,11 @@ class NexusMessaging {
41
41
  keyword: [],
42
42
  flow: []
43
43
  };
44
- // Message batching
45
- this.pendingResponses = new Map();
44
+ // Message processing with check-after strategy
45
+ this.processingLocks = new Map(); // Per-chat locks to prevent parallel processing
46
46
  this.batchingConfig = {
47
- enabled: config.messageBatching?.enabled ?? false,
48
- baseWaitTime: config.messageBatching?.baseWaitTime ?? 10000
47
+ enabled: config.messageBatching?.enabled ?? true, // Enabled by default with check-after
48
+ checkDelayMs: config.messageBatching?.checkDelayMs ?? 100 // Delay before checking for new messages
49
49
  };
50
50
  }
51
51
 
@@ -393,10 +393,9 @@ class NexusMessaging {
393
393
  } else if (messageData.flow) {
394
394
  return await this.handleFlow(messageData);
395
395
  } else {
396
- // For regular messages and media, use batching if enabled
397
- logger.info('Batching config:', this.batchingConfig);
396
+ // For regular messages and media, use check-after processing
398
397
  if (this.batchingConfig.enabled && chatId) {
399
- return await this._handleWithBatching(messageData, chatId);
398
+ return await this._handleWithCheckAfter(chatId);
400
399
  } else {
401
400
  if (messageData.media) {
402
401
  return await this.handleMedia(messageData);
@@ -649,77 +648,52 @@ class NexusMessaging {
649
648
  }
650
649
 
651
650
  /**
652
- * Handle message with batching - waits for additional messages before processing
651
+ * Handle message with check-after strategy - process immediately, check for new messages after
653
652
  */
654
- async _handleWithBatching(messageData, chatId) {
655
- const existing = this.pendingResponses.get(chatId);
656
- if (existing) {
657
- clearTimeout(existing.timeoutId);
658
- if (existing.typingInterval) {
659
- clearInterval(existing.typingInterval);
660
- }
661
- logger.info(`Received additional message from ${chatId}, resetting wait timer`);
662
- }
663
-
664
- // Start typing indicator refresh for batching period
665
- const typingInterval = await this._startTypingRefresh(chatId);
666
-
667
- const waitTime = this.batchingConfig.baseWaitTime;
668
- const timeoutId = setTimeout(async () => {
669
- try {
670
- if (typingInterval) {
671
- clearInterval(typingInterval);
672
- }
673
- this.pendingResponses.delete(chatId);
674
- await this._handleBatchedMessages(chatId);
675
- } catch (error) {
676
- logger.error(`Error handling batched messages for ${chatId}:`, error);
677
- }
678
- }, waitTime);
679
-
680
- this.pendingResponses.set(chatId, { timeoutId, typingInterval });
681
- logger.info(`Waiting ${Math.round(waitTime/1000)} seconds for more messages from ${chatId}`);
682
- }
683
-
684
- /**
685
- * Start typing indicator refresh interval
686
- */
687
- async _startTypingRefresh(chatId) {
688
- if (!this.provider || typeof this.provider.sendTypingIndicator !== 'function') {
689
- return null;
653
+ async _handleWithCheckAfter(chatId) {
654
+ // If already processing this chat, just return (message is saved, will be picked up)
655
+ if (this.processingLocks.has(chatId)) {
656
+ logger.info(`[CheckAfter] Already processing ${chatId}, new message will be included`);
657
+ return;
690
658
  }
691
659
 
692
- const lastMessage = await Message.findOne({
693
- numero: chatId,
694
- from_me: false,
695
- message_id: { $exists: true, $ne: null }
696
- }).sort({ createdAt: -1 });
697
-
698
- if (!lastMessage?.message_id) return null;
699
-
700
- return setInterval(() =>
701
- this.provider.sendTypingIndicator(lastMessage.message_id).catch(err =>
702
- logger.debug('[_startTypingRefresh] Failed', { error: err.message })
703
- ), 5000
704
- );
660
+ await this._processWithLock(chatId);
705
661
  }
706
662
 
707
663
  /**
708
- * Process all batched messages for a chat
664
+ * Process messages with per-chat lock and check-after logic
709
665
  */
710
- async _handleBatchedMessages(chatId) {
666
+ async _processWithLock(chatId) {
667
+ this.processingLocks.set(chatId, true);
711
668
  let typingInterval = null;
712
-
669
+
713
670
  try {
714
- logger.info(`Processing batched messages from ${chatId} (including media if any)`);
715
-
716
671
  typingInterval = await this._startTypingRefresh(chatId);
717
-
718
- // Get assistant response
672
+ logger.info(`[CheckAfter] Processing messages for ${chatId}`);
673
+
674
+ // Process with assistant
719
675
  const result = await replyAssistant(chatId);
720
676
  const botResponse = typeof result === 'string' ? result : result?.output;
721
677
  const tools_executed = typeof result === 'object' ? result?.tools_executed : undefined;
722
-
678
+
679
+ // Small delay to catch very recent DB writes
680
+ await new Promise(resolve => setTimeout(resolve, this.batchingConfig.checkDelayMs));
681
+
682
+ // Check for new unprocessed messages
683
+ const hasNewMessages = await Message.exists({
684
+ numero: chatId,
685
+ processed: false,
686
+ from_me: false
687
+ });
688
+
689
+ if (hasNewMessages) {
690
+ logger.info(`[CheckAfter] New messages detected for ${chatId}, discarding response and reprocessing`);
691
+ if (typingInterval) clearInterval(typingInterval);
692
+ // Recursively process with new messages
693
+ return await this._processWithLock(chatId);
694
+ }
695
+
696
+ // No new messages - send response and mark processed
723
697
  if (botResponse) {
724
698
  await this.sendMessage({
725
699
  code: chatId,
@@ -729,40 +703,45 @@ class NexusMessaging {
729
703
  tools_executed
730
704
  });
731
705
  }
732
-
733
- // Emit event for batched processing
734
- this.events.emit('messages:batched', { chatId, response: botResponse });
735
-
706
+
707
+ this.events.emit('messages:processed', { chatId, response: botResponse });
708
+
736
709
  } catch (error) {
737
- logger.error('Error in batched message handling:', { error: error.message });
710
+ logger.error('[CheckAfter] Error processing messages:', { chatId, error: error.message });
738
711
  } finally {
739
- if (typingInterval) {
740
- clearInterval(typingInterval);
741
- }
712
+ if (typingInterval) clearInterval(typingInterval);
713
+ this.processingLocks.delete(chatId);
742
714
  }
743
715
  }
744
716
 
745
717
  /**
746
- * Clear pending response for a chat (useful for cleanup)
718
+ * Start typing indicator refresh interval
747
719
  */
748
- clearPendingResponse(chatId) {
749
- const pending = this.pendingResponses.get(chatId);
750
- if (pending) {
751
- if (pending.timeoutId) {
752
- clearTimeout(pending.timeoutId);
753
- }
754
- if (pending.typingInterval) {
755
- clearInterval(pending.typingInterval);
756
- }
757
- this.pendingResponses.delete(chatId);
720
+ async _startTypingRefresh(chatId) {
721
+ if (!this.provider || typeof this.provider.sendTypingIndicator !== 'function') {
722
+ return null;
758
723
  }
724
+
725
+ const lastMessage = await Message.findOne({
726
+ numero: chatId,
727
+ from_me: false,
728
+ message_id: { $exists: true, $ne: null }
729
+ }).sort({ createdAt: -1 });
730
+
731
+ if (!lastMessage?.message_id) return null;
732
+
733
+ return setInterval(() =>
734
+ this.provider.sendTypingIndicator(lastMessage.message_id).catch(err =>
735
+ logger.debug('[_startTypingRefresh] Failed', { error: err.message })
736
+ ), 5000
737
+ );
759
738
  }
760
739
 
761
740
  /**
762
- * Get batching status for a chat
741
+ * Check if chat is currently being processed
763
742
  */
764
- hasPendingResponse(chatId) {
765
- return this.pendingResponses.has(chatId);
743
+ isProcessing(chatId) {
744
+ return this.processingLocks.has(chatId);
766
745
  }
767
746
  }
768
747
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",