@peopl-health/nexus 3.1.1 → 3.1.3

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.
@@ -480,7 +480,7 @@ class NexusMessaging {
480
480
  return;
481
481
  }
482
482
 
483
- const result = await replyAssistant(from, body);
483
+ const result = await this._processMessages(from, () => replyAssistant(from, body));
484
484
  const response = typeof result === 'string' ? result : result?.output;
485
485
  const tools_executed = typeof result === 'object' ? result?.tools_executed : undefined;
486
486
 
@@ -547,7 +547,7 @@ class NexusMessaging {
547
547
  ? body
548
548
  : `Media received (${mediaDescriptor || 'attachment'})`;
549
549
 
550
- const result = await replyAssistant(from, fallbackMessage);
550
+ const result = await this._processMessages(from, () => replyAssistant(from, fallbackMessage));
551
551
  const response = typeof result === 'string' ? result : result?.output;
552
552
  const tools_executed = typeof result === 'object' ? result?.tools_executed : undefined;
553
553
 
@@ -718,7 +718,7 @@ class NexusMessaging {
718
718
  runId = `run_${Date.now()}_${Math.random().toString(36).substring(7)}`;
719
719
  this.activeRequests.set(chatId, runId);
720
720
 
721
- const result = await replyAssistant(chatId, null, null, { runId });
721
+ const result = await this._processMessages(chatId, () => replyAssistant(chatId, null, null, { runId }));
722
722
 
723
723
  if (this.abandonedRuns.has(runId)) {
724
724
  logger.info(`[CheckAfter] Discarding abandoned run ${runId} for ${chatId}`);
@@ -743,7 +743,7 @@ class NexusMessaging {
743
743
  tools_executed
744
744
  });
745
745
  }
746
-
746
+
747
747
  this.events.emit('messages:processed', { chatId, response: botResponse });
748
748
 
749
749
  } catch (error) {
@@ -762,7 +762,6 @@ class NexusMessaging {
762
762
  * Get count of unprocessed messages for a chat
763
763
  */
764
764
  async _getUnprocessedMessageCount(chatId) {
765
- const { Message } = require('../models/messageModel');
766
765
  return await Message.countDocuments({
767
766
  numero: chatId,
768
767
  processed: false,
@@ -770,6 +769,34 @@ class NexusMessaging {
770
769
  });
771
770
  }
772
771
 
772
+ /**
773
+ * Central processing pipeline - handles status updates for any processing function
774
+ */
775
+ async _processMessages(chatId, processingFn) {
776
+ const unprocessedMessages = await Message.find({
777
+ numero: chatId,
778
+ from_me: false,
779
+ processed: false
780
+ }).select('_id');
781
+
782
+ try {
783
+ const result = await processingFn();
784
+
785
+ if (unprocessedMessages.length > 0) {
786
+ await Message.updateMany(
787
+ { _id: { $in: unprocessedMessages.map(m => m._id) } },
788
+ { $set: { processed: true } }
789
+ );
790
+ logger.info(`[_processMessages] Marked ${unprocessedMessages.length} specific messages as processed for ${chatId}`);
791
+ }
792
+
793
+ return result;
794
+ } catch (error) {
795
+ logger.debug(`[_processMessages] Processing failed, messages remain unprocessed for ${chatId}`);
796
+ throw error;
797
+ }
798
+ }
799
+
773
800
  /**
774
801
  * Start typing indicator refresh interval
775
802
  */
@@ -781,16 +808,16 @@ class NexusMessaging {
781
808
  const lastMessage = await Message.findOne({
782
809
  numero: chatId,
783
810
  from_me: false,
784
- message_id: { $exists: true, $ne: null, $not: /^pending-/ },
785
- createdAt: { $gte: new Date(Date.now() - 24 * 60 * 60 * 1000) }
811
+ processed: false,
812
+ message_id: { $exists: true, $ne: null, $not: /^pending-/ }
786
813
  }).sort({ createdAt: -1 });
787
814
 
788
815
  if (!lastMessage?.message_id) {
789
- logger.debug(`[_startTypingRefresh] No valid message for typing indicator: ${chatId}`);
816
+ logger.debug(`[_startTypingRefresh] No unprocessed message for typing indicator: ${chatId}`);
790
817
  return null;
791
818
  }
792
819
 
793
- logger.debug(`[_startTypingRefresh] Starting typing indicator for message: ${lastMessage.message_id}`);
820
+ logger.debug(`[_startTypingRefresh] Starting typing indicator for unprocessed message: ${lastMessage.message_id}`);
794
821
 
795
822
  return setInterval(() =>
796
823
  this.provider.sendTypingIndicator(lastMessage.message_id).catch(err =>
@@ -182,7 +182,11 @@ async function downloadMediaAndCreateFile(code, reply) {
182
182
 
183
183
  const sanitizedCode = sanitizeFilename(code, 20);
184
184
  const sanitizedSubType = sanitizeFilename(subType, 10);
185
- const sanitizedFileName = sanitizeFilename(fileName, 50);
185
+
186
+ const fileExt = path.extname(fileName);
187
+ const fileBaseName = path.basename(fileName, fileExt);
188
+ const sanitizedBaseName = sanitizeFilename(fileBaseName, 50 - fileExt.length);
189
+ const sanitizedFileName = sanitizedBaseName + fileExt;
186
190
 
187
191
  const sourceFile = `${sanitizedCode}-${sanitizedSubType}-${sanitizedFileName}`;
188
192
  const downloadPath = path.join(__dirname, 'assets', 'tmp', sourceFile);
@@ -147,41 +147,9 @@ const processAudioFileCore = async (fileName, provider) => {
147
147
  };
148
148
 
149
149
  try {
150
- const fileExtension = fileName.split('.').pop().toLowerCase();
151
- const needsConversion = fileExtension === 'ogg'; // Convert OGG for OpenAI compatibility
152
- let transcriptionFile = fileName;
153
- let convertedFile = null;
154
-
155
- if (needsConversion) {
156
- logger.info('[processAudioFile] Converting OGG to MP3 for transcription', {
157
- originalFile: fileName ? fileName.split('/').pop().replace(/^[^-]+-[^-]+-/, 'xxx-xxx-') : 'unknown',
158
- format: fileExtension
159
- });
160
-
161
- convertedFile = fileName.replace('.ogg', '_converted.mp3');
162
-
163
- try {
164
- const { execSync } = require('child_process');
165
- execSync(`ffmpeg -i "${fileName}" -acodec mp3 -ab 128k "${convertedFile}" -y`, {
166
- stdio: 'pipe'
167
- });
168
- transcriptionFile = convertedFile;
169
-
170
- logger.info('[processAudioFile] Conversion successful', {
171
- convertedFile: convertedFile.split('/').pop().replace(/^[^-]+-[^-]+-/, 'xxx-xxx-')
172
- });
173
- } catch (conversionError) {
174
- logger.error('[processAudioFile] Conversion failed, attempting with original file', {
175
- error: conversionError.message,
176
- originalFile: fileName ? fileName.split('/').pop().replace(/^[^-]+-[^-]+-/, 'xxx-xxx-') : 'unknown'
177
- });
178
- transcriptionFile = fileName;
179
- }
180
- }
181
-
182
150
  const { result: audioTranscript, duration: transcribeDuration } = await withTracing(
183
151
  async () => provider.transcribeAudio({
184
- file: fs.createReadStream(transcriptionFile),
152
+ file: fs.createReadStream(fileName),
185
153
  responseFormat: 'text',
186
154
  language: 'es'
187
155
  }),
@@ -189,17 +157,6 @@ const processAudioFileCore = async (fileName, provider) => {
189
157
  () => ({ 'audio.file_name': fileName ? fileName.split('/').pop().replace(/^[^-]+-[^-]+-/, 'xxx-xxx-') : 'unknown' }),
190
158
  { returnTiming: true }
191
159
  )();
192
-
193
- if (convertedFile && fs.existsSync(convertedFile)) {
194
- try {
195
- fs.unlinkSync(convertedFile);
196
- logger.debug('[processAudioFile] Cleaned up converted file');
197
- } catch (cleanupError) {
198
- logger.warn('[processAudioFile] Failed to cleanup converted file', {
199
- error: cleanupError.message
200
- });
201
- }
202
- }
203
160
  timings.transcribe_ms = transcribeDuration;
204
161
 
205
162
  const transcriptText = audioTranscript?.text || audioTranscript;
@@ -190,15 +190,6 @@ const replyAssistantCore = async (code, message_ = null, thread_ = null, runOpti
190
190
  const urls = processResults.filter(r => r.url).map(r => ({ url: r.url }));
191
191
  const allTempFiles = processResults.flatMap(r => r.tempFiles || []);
192
192
 
193
- await Promise.all(processResults.map(r => {
194
- const processedContent = r.messages && r.messages.length > 0
195
- ? r.messages
196
- .filter(msg => msg.content.text !== r.reply?.body)
197
- .map(msg => msg.content.text)
198
- .join(' ')
199
- : null;
200
- return updateMessageRecord(r.reply, finalThread, processedContent);
201
- }));
202
193
  await cleanupFiles(allTempFiles);
203
194
 
204
195
  if (urls.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peopl-health/nexus",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "description": "Core messaging and assistant library for WhatsApp communication platforms",
5
5
  "keywords": [
6
6
  "whatsapp",