whatsapp-pi 1.0.62 → 1.0.64

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/README.md CHANGED
@@ -19,7 +19,6 @@ Pi is a powerful agentic AI coding assistant that operates in your terminal. Thi
19
19
  - Manage aliases and print allowed contacts from the menu
20
20
  - **Allowed Groups**: Control which WhatsApp groups can interact with Pi
21
21
  - Add group JIDs with optional aliases
22
- - Choose reaction mode per group: **Active** (reply to all allowed group messages) or **Passive** (reply only when mentioned with @)
23
22
  - Only groups in Allowed Groups are processed by the agent
24
23
  - **Recents & History**: Browse recent conversations, inspect full message history, and reply from message detail view
25
24
  - **Reliable Messaging**: Queue-based message sending with retry logic
@@ -127,6 +126,20 @@ pi -e whatsapp-pi.ts --whatsapp-pi-online
127
126
  - **Send Message** and `send_wa_message` are outbound only.
128
127
  - If you message yourself, WhatsApp may show sent/read ticks, but that does not guarantee Pi will treat it as a trigger.
129
128
 
129
+ ## LLM-Callable Tools
130
+
131
+ The extension registers the following tools that the Pi agent can call:
132
+
133
+ | Tool | Direction | Description |
134
+ | --- | --- | --- |
135
+ | `send_wa_message` | outbound | Send a WhatsApp message to a contact or group (or reply to the last conversation if `jid` is omitted). |
136
+ | `send_reaction` | outbound | React to a WhatsApp message with an emoji. |
137
+ | `list_wa_conversations` | read-only | List recent conversations from the local recents store. Supports `onlyIncoming`, `onlyAllowed`, and `limit`. |
138
+ | `get_wa_conversation_history` | read-only | Get the most recent messages with a given `senderNumber` (accepts `+E164`, raw digits, or a JID). Supports `limit`. |
139
+ | `check_wa_new_messages` | read-only | List conversations whose most recent message is incoming (i.e. waiting for a reply). Supports `sinceTimestamp` (ms epoch). |
140
+
141
+ The three read-only tools query the local recents store at `~/.pi/whatsapp-pi/recents/recents.json`. They never touch the network and do not mark messages as read.
142
+
130
143
  ## WhatsApp Numbers and JIDs
131
144
 
132
145
  - Contacts use phone format in UI: `+5511999999999`
@@ -153,8 +166,8 @@ pi -e whatsapp-pi.ts --whatsapp-pi-online
153
166
 
154
167
  ### Allowed Groups Management
155
168
  - **Add Group** - Add a WhatsApp group JID to the allowed groups list (format: 120363012345@g.us)
156
- - **Select a group** - Open a submenu with **History**, **Send Message**, **Print Group JID**, **Reaction Mode**, **Add Alias**, **Remove Alias**, **Remove Group**, and **Back**
157
- - **Reaction Mode** - Switch between **Active** and **Passive** behavior for that group
169
+ - **Select a group** - Open a submenu with **History**, **Send Message**, **Print Group JID**, **Add Alias**, **Remove Alias**, **Remove Group**, and **Back**
170
+
158
171
  - **Back** - Return to main menu
159
172
 
160
173
  ### Recents Management
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whatsapp-pi",
3
- "version": "1.0.62",
3
+ "version": "1.0.64",
4
4
  "type": "module",
5
5
  "description": "WhatsApp integration extension for Pi",
6
6
  "main": "whatsapp-pi.ts",
package/src/i18n.ts CHANGED
@@ -194,6 +194,14 @@ const fallback = {
194
194
  "tool.sendReaction.label": "Send WhatsApp Reaction",
195
195
  "tool.sendReaction.description": "React to a WhatsApp message with an emoji",
196
196
  "tool.sendReaction.error.invalidEmoji": "Invalid emoji provided",
197
+ "tool.listConversations.label": "List WhatsApp Conversations",
198
+ "tool.listConversations.description": "List recent WhatsApp conversations from the local recents store (sender number, optional name, last message preview, direction, timestamp, allowed flag). Read-only.",
199
+ "tool.getHistory.label": "Get WhatsApp Conversation History",
200
+ "tool.getHistory.description": "Get the most recent messages exchanged with a given WhatsApp sender from the local recents store. Accepts a phone number (+E164 or raw digits) or a JID. Read-only.",
201
+ "tool.checkNew.label": "Check WhatsApp New Messages",
202
+ "tool.checkNew.description": "List recent WhatsApp conversations whose most recent message is incoming (i.e. waiting for a reply). Optionally filter by a `sinceTimestamp` (ms epoch). Read-only.",
203
+ "tool.error.notInitialized": "WhatsApp-Pi recents store not initialized",
204
+ "tool.error.missingSender": "senderNumber is required",
197
205
  "incoming.media.audioTranscribing": "[WhatsApp-Pi] Transcribing audio from {pushName}...",
198
206
  "incoming.media.audioTranscribed": "[Transcribed Audio]: {transcription}",
199
207
  "incoming.media.imageDownloading": "[WhatsApp-Pi] Downloading image from {pushName}...",
package/whatsapp-pi.ts CHANGED
@@ -401,6 +401,112 @@ export default function (pi: ExtensionAPI) {
401
401
  }
402
402
  });
403
403
 
404
+ // Register list_wa_conversations tool (LLM-callable, read-only)
405
+ pi.registerTool({
406
+ name: "list_wa_conversations",
407
+ label: t("tool.listConversations.label"),
408
+ description: t("tool.listConversations.description"),
409
+ promptSnippet: "list_wa_conversations({onlyIncoming?, onlyAllowed?, limit?}) - List recent WhatsApp conversations from the local recents store. Read-only; safe to call any time.",
410
+ parameters: Type.Object({
411
+ onlyIncoming: Type.Optional(Type.Boolean({ description: "Only return conversations whose last message is incoming (waiting for a reply)." })),
412
+ onlyAllowed: Type.Optional(Type.Boolean({ description: "Only return conversations from senders/groups currently in the allow list." })),
413
+ limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 20, description: "Maximum number of conversations to return (default 20)." }))
414
+ }),
415
+ async execute(_toolCallId, params) {
416
+ try {
417
+ const conversations = await recentsService.getRecentConversations();
418
+ let filtered = conversations;
419
+ if (params.onlyIncoming) {
420
+ filtered = filtered.filter(c => c.lastMessageDirection === 'incoming');
421
+ }
422
+ if (params.onlyAllowed) {
423
+ filtered = filtered.filter(c => c.isAllowed);
424
+ }
425
+ const limit = typeof params.limit === 'number' ? params.limit : 20;
426
+ filtered = filtered.slice(0, limit);
427
+ return {
428
+ isError: false,
429
+ details: undefined,
430
+ content: [{ type: "text" as const, text: JSON.stringify({ success: true, count: filtered.length, conversations: filtered }) }]
431
+ };
432
+ } catch (error) {
433
+ return {
434
+ isError: true,
435
+ details: undefined,
436
+ content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }) }]
437
+ };
438
+ }
439
+ }
440
+ });
441
+
442
+ // Register get_wa_conversation_history tool (LLM-callable, read-only)
443
+ pi.registerTool({
444
+ name: "get_wa_conversation_history",
445
+ label: t("tool.getHistory.label"),
446
+ description: t("tool.getHistory.description"),
447
+ promptSnippet: "get_wa_conversation_history({senderNumber, limit?}) - Get the most recent messages with a sender. `senderNumber` accepts +E164 (e.g. +14155551212), raw digits, or a JID (e.g. 14155551212@s.whatsapp.net, 120363012345@g.us). Read-only.",
448
+ parameters: Type.Object({
449
+ senderNumber: Type.String({ description: "Phone number (+E164 or raw digits) or WhatsApp JID of the conversation." }),
450
+ limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 20, description: "Maximum number of messages to return (default 20)." }))
451
+ }),
452
+ async execute(_toolCallId, params) {
453
+ if (!params.senderNumber || !params.senderNumber.trim()) {
454
+ return {
455
+ isError: true,
456
+ details: undefined,
457
+ content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: t("tool.error.missingSender") }) }]
458
+ };
459
+ }
460
+ try {
461
+ const messages = await recentsService.getConversationHistory(params.senderNumber);
462
+ const limit = typeof params.limit === 'number' ? params.limit : 20;
463
+ const sliced = messages.slice(-limit);
464
+ return {
465
+ isError: false,
466
+ details: undefined,
467
+ content: [{ type: "text" as const, text: JSON.stringify({ success: true, count: sliced.length, messages: sliced }) }]
468
+ };
469
+ } catch (error) {
470
+ return {
471
+ isError: true,
472
+ details: undefined,
473
+ content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }) }]
474
+ };
475
+ }
476
+ }
477
+ });
478
+
479
+ // Register check_wa_new_messages tool (LLM-callable, read-only)
480
+ pi.registerTool({
481
+ name: "check_wa_new_messages",
482
+ label: t("tool.checkNew.label"),
483
+ description: t("tool.checkNew.description"),
484
+ promptSnippet: "check_wa_new_messages({sinceTimestamp?}) - List conversations whose most recent message is incoming (i.e. waiting for a reply). Optional `sinceTimestamp` (ms epoch) filters to messages newer than that. Read-only.",
485
+ parameters: Type.Object({
486
+ sinceTimestamp: Type.Optional(Type.Integer({ minimum: 0, description: "Only include conversations whose last incoming message timestamp is strictly greater than this (ms since epoch)." }))
487
+ }),
488
+ async execute(_toolCallId, params) {
489
+ try {
490
+ const conversations = await recentsService.getRecentConversations();
491
+ const since = typeof params.sinceTimestamp === 'number' ? params.sinceTimestamp : 0;
492
+ const pending = conversations.filter(c =>
493
+ c.lastMessageDirection === 'incoming' && c.lastMessageTime > since
494
+ );
495
+ return {
496
+ isError: false,
497
+ details: undefined,
498
+ content: [{ type: "text" as const, text: JSON.stringify({ success: true, count: pending.length, conversations: pending }) }]
499
+ };
500
+ } catch (error) {
501
+ return {
502
+ isError: true,
503
+ details: undefined,
504
+ content: [{ type: "text" as const, text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) }) }]
505
+ };
506
+ }
507
+ }
508
+ });
509
+
404
510
  // Suppress automatic message_end reply when tool already sent
405
511
  // This is checked by the message_end handler below
406
512