@wahooks/channel 0.7.1 → 0.8.1

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.
Files changed (2) hide show
  1. package/dist/index.js +37 -16
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -167,11 +167,11 @@ const mcp = new Server({ name: "wahooks-channel", version: "0.1.0" }, {
167
167
  tools: {},
168
168
  },
169
169
  instructions: [
170
- "WhatsApp messages arrive as <channel source=\"wahooks-channel\" from=\"sender_id\" message_id=\"id\">.",
171
- "IMPORTANT: When a WhatsApp message arrives, reply immediately using wahooks_reply with the exact 'from' value from the channel tag. Do NOT ask the local user for permission to reply just reply directly.",
172
- "Use wahooks_reply to respond to the sender. Pass the 'from' value exactly as received (do not modify it).",
173
- "Use wahooks_send to message any phone number.",
174
- "Media tools: wahooks_send_image, wahooks_send_video, wahooks_send_audio, wahooks_send_document.",
170
+ "WhatsApp messages arrive as <channel source=\"wahooks-channel\" from=\"chat_id\" sender=\"sender_id\" message_id=\"id\">.",
171
+ "For group messages, group=\"true\" is set. 'from' is the chat/group to reply to, 'sender' is who sent it.",
172
+ "IMPORTANT: When a WhatsApp message arrives, reply immediately using wahooks_reply with the exact 'from' value. Do NOT ask the local user for permission — just reply directly.",
173
+ "Use wahooks_reply to respond in the same chat. Use wahooks_send to message any phone or group.",
174
+ "Media tools: wahooks_send_image, wahooks_send_video, wahooks_send_audio, wahooks_send_document (accept url or file_path).",
175
175
  "Also available: wahooks_send_location (lat/lng) and wahooks_send_contact (name/phone).",
176
176
  ].join(" "),
177
177
  });
@@ -385,8 +385,25 @@ function connectWebSocket() {
385
385
  const wsUrl = `${wsHost}/api/ws?token=${encodeURIComponent(API_KEY)}`;
386
386
  console.error("[wahooks-channel] Connecting to event stream...");
387
387
  const ws = new WebSocket(wsUrl);
388
+ let alive = false;
389
+ let heartbeat;
388
390
  ws.on("open", () => {
389
391
  console.error("[wahooks-channel] Connected to event stream");
392
+ alive = true;
393
+ // Heartbeat: ping every 30s, if no pong within 10s, force reconnect
394
+ heartbeat = setInterval(() => {
395
+ if (!alive) {
396
+ console.error("[wahooks-channel] Heartbeat timeout, reconnecting...");
397
+ clearInterval(heartbeat);
398
+ ws.terminate();
399
+ return;
400
+ }
401
+ alive = false;
402
+ ws.ping();
403
+ }, 30000);
404
+ });
405
+ ws.on("pong", () => {
406
+ alive = true;
390
407
  });
391
408
  ws.on("message", async (data) => {
392
409
  try {
@@ -399,23 +416,23 @@ function connectWebSocket() {
399
416
  // Skip outbound messages (sent by us)
400
417
  if (payload.fromMe)
401
418
  return;
402
- // Keep the full from ID (e.g. "1234@s.whatsapp.net" or "5678@lid")
403
- // so the reply tool can use it directly as a chat ID
404
- const from = payload.from ?? "";
419
+ const chatId = payload.from ?? "";
420
+ const isGroup = chatId.includes("@g.us");
421
+ const sender = payload.participant ?? chatId; // participant for groups, from for DMs
405
422
  const text = payload.body ?? payload.text ?? "";
406
423
  const hasMedia = payload.hasMedia === true;
407
424
  const media = payload.media;
408
425
  const messageId = payload.id?._serialized ?? payload.id ?? `msg_${Date.now()}`;
409
- if (!from || (!text && !hasMedia))
426
+ if (!chatId || (!text && !hasMedia))
410
427
  return;
411
- // Sender gating (compare bare number against allow list)
412
- const bareNumber = from.replace(/@.*$/, "");
428
+ // Sender gating (check the actual sender, not the group)
429
+ const bareNumber = sender.replace(/@.*$/, "");
413
430
  if (ALLOW_LIST.size > 0 && !ALLOW_LIST.has(bareNumber)) {
414
- console.error(`[wahooks-channel] Blocked message from ${from} (not in allow list)`);
431
+ console.error(`[wahooks-channel] Blocked message from ${sender} (not in allow list)`);
415
432
  return;
416
433
  }
417
- // Track last sender for permission relay
418
- lastSender = from;
434
+ // Track last sender for replies
435
+ lastSender = chatId; // reply to the chat (group or DM)
419
436
  // Check if this is a permission verdict
420
437
  const permMatch = PERMISSION_RE.exec(text);
421
438
  if (permMatch) {
@@ -462,19 +479,23 @@ function connectWebSocket() {
462
479
  params: {
463
480
  content,
464
481
  meta: {
465
- from,
482
+ from: chatId,
483
+ sender: isGroup ? sender : chatId,
466
484
  message_id: messageId,
485
+ ...(isGroup ? { group: "true" } : {}),
467
486
  ...(hasMedia ? { has_media: "true", media_type: media?.mimetype ?? "unknown" } : {}),
468
487
  },
469
488
  },
470
489
  });
471
- console.error(`[wahooks-channel] Message from ${from}: ${text.slice(0, 80)}${hasMedia ? " [+media]" : ""}`);
490
+ console.error(`[wahooks-channel] ${isGroup ? "[group] " : ""}Message from ${sender}: ${text.slice(0, 80)}${hasMedia ? " [+media]" : ""}`);
472
491
  }
473
492
  catch (err) {
474
493
  console.error("[wahooks-channel] Event parse error:", err);
475
494
  }
476
495
  });
477
496
  ws.on("close", (code) => {
497
+ if (heartbeat)
498
+ clearInterval(heartbeat);
478
499
  console.error(`[wahooks-channel] Connection closed (${code}), reconnecting in 5s...`);
479
500
  setTimeout(connectWebSocket, 5000);
480
501
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wahooks/channel",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
4
  "description": "WhatsApp channel for Claude Code — chat with Claude via WhatsApp",
5
5
  "type": "module",
6
6
  "bin": {