@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.
- package/examples/basic-usage.js +3 -4
- package/lib/core/NexusMessaging.js +66 -87
- package/package.json +1 -1
package/examples/basic-usage.js
CHANGED
|
@@ -66,13 +66,12 @@ class GeneralAssistant extends BaseAssistant {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
async function startServer() {
|
|
69
|
-
// Initialize Nexus with
|
|
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
|
-
|
|
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
|
|
45
|
-
this.
|
|
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 ??
|
|
48
|
-
|
|
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
|
|
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.
|
|
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
|
|
651
|
+
* Handle message with check-after strategy - process immediately, check for new messages after
|
|
653
652
|
*/
|
|
654
|
-
async
|
|
655
|
-
|
|
656
|
-
if (
|
|
657
|
-
|
|
658
|
-
|
|
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
|
-
|
|
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
|
|
664
|
+
* Process messages with per-chat lock and check-after logic
|
|
709
665
|
*/
|
|
710
|
-
async
|
|
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
|
-
|
|
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
|
-
|
|
734
|
-
|
|
735
|
-
|
|
706
|
+
|
|
707
|
+
this.events.emit('messages:processed', { chatId, response: botResponse });
|
|
708
|
+
|
|
736
709
|
} catch (error) {
|
|
737
|
-
logger.error('Error
|
|
710
|
+
logger.error('[CheckAfter] Error processing messages:', { chatId, error: error.message });
|
|
738
711
|
} finally {
|
|
739
|
-
if (typingInterval)
|
|
740
|
-
|
|
741
|
-
}
|
|
712
|
+
if (typingInterval) clearInterval(typingInterval);
|
|
713
|
+
this.processingLocks.delete(chatId);
|
|
742
714
|
}
|
|
743
715
|
}
|
|
744
716
|
|
|
745
717
|
/**
|
|
746
|
-
*
|
|
718
|
+
* Start typing indicator refresh interval
|
|
747
719
|
*/
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
*
|
|
741
|
+
* Check if chat is currently being processed
|
|
763
742
|
*/
|
|
764
|
-
|
|
765
|
-
return this.
|
|
743
|
+
isProcessing(chatId) {
|
|
744
|
+
return this.processingLocks.has(chatId);
|
|
766
745
|
}
|
|
767
746
|
}
|
|
768
747
|
|