whatsapp-pi 1.0.60 → 1.0.61

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-pi",
3
- "version": "1.0.60",
3
+ "version": "1.0.61",
4
4
  "type": "module",
5
5
  "description": "WhatsApp integration extension for Pi",
6
6
  "main": "whatsapp-pi.ts",
@@ -185,11 +185,27 @@ export class WhatsAppService {
185
185
  return value;
186
186
  }
187
187
 
188
- private normalizeRecipientJid(jid: string): string {
189
- if (jid.includes('@')) return jid;
190
- const digits = jid.startsWith('+') ? jid.slice(1) : jid;
191
- return `${digits}@s.whatsapp.net`;
192
- }
188
+ private normalizeRecipientJid(jid: string): string {
189
+ if (jid.includes('@')) return jid;
190
+ const digits = jid.startsWith('+') ? jid.slice(1) : jid;
191
+ return `${digits}@s.whatsapp.net`;
192
+ }
193
+
194
+ public resolveOutboundRecipientJid(recipient: string): string {
195
+ if (SessionManager.isGroupJid(recipient)) {
196
+ return recipient;
197
+ }
198
+
199
+ const senderNumber = this.normalizeContactNumber(recipient.split('@')[0]);
200
+ const allowedContact = this.sessionManager.getAllowedContact(recipient)
201
+ ?? this.sessionManager.getAllowedContact(senderNumber);
202
+
203
+ if (allowedContact?.sendNumber) {
204
+ return this.normalizeRecipientJid(allowedContact.sendNumber);
205
+ }
206
+
207
+ return this.normalizeRecipientJid(recipient);
208
+ }
193
209
 
194
210
  private normalizeJidForComparison(jid: string): string {
195
211
  const [localPart, domain = ''] = jid.split('@');
@@ -653,27 +669,29 @@ export class WhatsAppService {
653
669
  }
654
670
  }
655
671
 
656
- async sendMessage(jid: string, text: string) {
657
- // Ensure we show the typing indicator before sending
658
- await this.sendPresence(jid, 'composing');
659
-
660
- const result = await this.messageSender.send({
661
- recipientJid: jid,
662
- text: text
663
- });
664
-
665
- // After sending, we can stop the typing indicator
666
- await this.sendPresence(jid, 'paused');
667
-
668
- if (!result.success) {
669
- console.error(t('service.whatsapp.failedSendMessage', { jid, error: result.error ?? t('message.sender.unknownError') }));
670
- }
671
-
672
- return result;
673
- }
674
-
675
- async sendMenuMessage(jid: string, text: string) {
676
- const normalizedJid = this.normalizeRecipientJid(jid);
672
+ async sendMessage(jid: string, text: string) {
673
+ const recipientJid = this.resolveOutboundRecipientJid(jid);
674
+
675
+ // Ensure we show the typing indicator before sending
676
+ await this.sendPresence(recipientJid, 'composing');
677
+
678
+ const result = await this.messageSender.send({
679
+ recipientJid,
680
+ text: text
681
+ });
682
+
683
+ // After sending, we can stop the typing indicator
684
+ await this.sendPresence(recipientJid, 'paused');
685
+
686
+ if (!result.success) {
687
+ console.error(t('service.whatsapp.failedSendMessage', { jid: recipientJid, error: result.error ?? t('message.sender.unknownError') }));
688
+ }
689
+
690
+ return result;
691
+ }
692
+
693
+ async sendMenuMessage(jid: string, text: string) {
694
+ const normalizedJid = this.resolveOutboundRecipientJid(jid);
677
695
  const socket = this.getActiveSocket();
678
696
 
679
697
  if (!socket) {
@@ -41,13 +41,21 @@ const buildReplyWidget = (selectedMessage: SelectedMessageContext): string[] =>
41
41
  ];
42
42
  };
43
43
 
44
- const buildReplyTitle = (selectedMessage: SelectedMessageContext): string => {
44
+ const buildReplyTitle = (selectedMessage: SelectedMessageContext): string => {
45
45
  const sender = selectedMessage.senderName
46
46
  ? `${selectedMessage.senderName} (${selectedMessage.senderNumber})`
47
47
  : selectedMessage.senderNumber;
48
48
 
49
- return truncateToWidth(`${t('message.reply.title')} ${sender}`, 120);
50
- };
49
+ return truncateToWidth(`${t('message.reply.title')} ${sender}`, 120);
50
+ };
51
+
52
+ const toRecentSenderNumber = (recipientJid: string): string => {
53
+ if (recipientJid.endsWith('@g.us')) {
54
+ return recipientJid;
55
+ }
56
+
57
+ return `+${recipientJid.split('@')[0]}`;
58
+ };
51
59
 
52
60
  export async function showMessageReplyView(
53
61
  ctx: MessageReplyContext,
@@ -70,24 +78,27 @@ export async function showMessageReplyView(
70
78
  continue;
71
79
  }
72
80
 
73
- const draft: ReplyDraft = {
74
- text,
75
- targetMessageId: props.selectedMessage.messageId,
76
- targetConversation: props.selectedMessage.senderNumber
77
- };
78
-
79
- const result: ReplySendResult = await props.whatsappService.sendMenuMessage(
80
- props.selectedMessage.senderNumber,
81
- draft.text
82
- );
83
-
84
- if (result.success) {
85
- await props.recentsService.recordMessage({
86
- messageId: result.messageId ?? `${Date.now()}`,
87
- senderNumber: props.selectedMessage.senderNumber,
88
- senderName: props.selectedMessage.senderName,
89
- text: draft.text,
90
- direction: 'outgoing',
81
+ const draft: ReplyDraft = {
82
+ text,
83
+ targetMessageId: props.selectedMessage.messageId,
84
+ targetConversation: props.selectedMessage.senderNumber
85
+ };
86
+ const recipientJid = props.whatsappService.resolveOutboundRecipientJid(
87
+ props.selectedMessage.senderNumber
88
+ );
89
+
90
+ const result: ReplySendResult = await props.whatsappService.sendMenuMessage(
91
+ recipientJid,
92
+ draft.text
93
+ );
94
+
95
+ if (result.success) {
96
+ await props.recentsService.recordMessage({
97
+ messageId: result.messageId ?? `${Date.now()}`,
98
+ senderNumber: toRecentSenderNumber(recipientJid),
99
+ senderName: props.selectedMessage.senderName,
100
+ text: draft.text,
101
+ direction: 'outgoing',
91
102
  timestamp: Date.now()
92
103
  });
93
104
  ctx.ui.notify(t('message.reply.sent', { preview: buildPreview(props.selectedMessage.text) }), 'info');
package/whatsapp-pi.ts CHANGED
@@ -205,8 +205,16 @@ export default function (pi: ExtensionAPI) {
205
205
  ctx.ui.notify('WhatsApp: Session reset via /new is now fully supported.', 'info');
206
206
  });
207
207
 
208
- // Track whether send_wa_message tool already sent a reply this turn
209
- let toolSentToJid: string | null = null;
208
+ // Track whether send_wa_message tool already sent a reply this turn
209
+ let toolSentToJid: string | null = null;
210
+
211
+ const toRecentSenderNumber = (recipientJid: string): string => {
212
+ if (recipientJid.endsWith('@g.us')) {
213
+ return recipientJid;
214
+ }
215
+
216
+ return `+${recipientJid.split('@')[0]}`;
217
+ };
210
218
 
211
219
  // Handle incoming messages by injecting them as user prompts
212
220
  whatsappService.setMessageCallback(async (m) => {
@@ -318,16 +326,15 @@ export default function (pi: ExtensionAPI) {
318
326
  formattedMessage
319
327
  ].join('\n'));
320
328
 
321
- const result = await whatsappService.sendMessage(resolvedJid, params.message);
329
+ const outboundJid = whatsappService.resolveOutboundRecipientJid(resolvedJid);
330
+ const result = await whatsappService.sendMessage(outboundJid, params.message);
322
331
 
323
332
  if (result.success) {
324
333
  // Mark that tool already sent to this JID — prevents message_end from re-sending
325
- toolSentToJid = resolvedJid;
326
- const isGroupJid = resolvedJid.endsWith('@g.us');
327
- const senderNumber = isGroupJid ? resolvedJid : `+${resolvedJid.split('@')[0]}`;
328
- await recentsService.recordMessage({
329
- messageId: result.messageId!,
330
- senderNumber,
334
+ toolSentToJid = outboundJid;
335
+ await recentsService.recordMessage({
336
+ messageId: result.messageId!,
337
+ senderNumber: toRecentSenderNumber(outboundJid),
331
338
  text: params.message,
332
339
  direction: 'outgoing',
333
340
  timestamp: Date.now()
@@ -378,10 +385,10 @@ export default function (pi: ExtensionAPI) {
378
385
  // Handle outgoing messages (Agent -> WhatsApp)
379
386
  pi.on("agent_start", async (_event, _ctx) => {
380
387
  if (sessionManager.getStatus() !== 'connected') return;
381
- const lastJid = whatsappService.getLastRemoteJid();
382
- if (lastJid) {
383
- await whatsappService.sendPresence(lastJid, 'composing');
384
- }
388
+ const lastJid = whatsappService.getLastRemoteJid();
389
+ if (lastJid) {
390
+ await whatsappService.sendPresence(whatsappService.resolveOutboundRecipientJid(lastJid), 'composing');
391
+ }
385
392
  });
386
393
 
387
394
  pi.on("message_end", async (event, ctx) => {
@@ -389,23 +396,26 @@ export default function (pi: ExtensionAPI) {
389
396
 
390
397
  const { message } = event;
391
398
  // Only reply if it's the assistant and we have a valid target
392
- if (message.role === "assistant") {
393
- const lastJid = whatsappService.getLastRemoteJid();
394
- const text = message.content.filter(c => c.type === "text").map(c => c.text).join("\n");
395
-
396
- // Skip if send_wa_message tool already sent a reply to this JID
397
- if (toolSentToJid === lastJid) {
398
- toolSentToJid = null;
399
- return;
400
- }
401
-
402
- if (lastJid && text) {
403
- try {
404
- const result = await whatsappService.sendMessage(lastJid, text);
405
- if (result.success) {
406
- await recentsService.recordMessage({
407
- messageId: result.messageId ?? `${Date.now()}`,
408
- senderNumber: `+${lastJid.split('@')[0]}`,
399
+ if (message.role === "assistant") {
400
+ const lastJid = whatsappService.getLastRemoteJid();
401
+ const text = message.content.filter(c => c.type === "text").map(c => c.text).join("\n");
402
+ const outboundJid = lastJid
403
+ ? whatsappService.resolveOutboundRecipientJid(lastJid)
404
+ : null;
405
+
406
+ // Skip if send_wa_message tool already sent a reply to this JID
407
+ if (toolSentToJid === outboundJid) {
408
+ toolSentToJid = null;
409
+ return;
410
+ }
411
+
412
+ if (outboundJid && text) {
413
+ try {
414
+ const result = await whatsappService.sendMessage(outboundJid, text);
415
+ if (result.success) {
416
+ await recentsService.recordMessage({
417
+ messageId: result.messageId ?? `${Date.now()}`,
418
+ senderNumber: toRecentSenderNumber(outboundJid),
409
419
  text,
410
420
  direction: 'outgoing',
411
421
  timestamp: Date.now()