@daolmedo/openclaw-twilio-whatsapp 1.1.21 → 1.1.23

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 +123 -35
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -58,35 +58,68 @@ function splitMessage(text) {
58
58
  return chunks;
59
59
  }
60
60
  async function sendTwilioWhatsappMessage(opts) {
61
- const client = twilio(opts.accountSid, opts.authToken);
61
+ const client = twilio(opts.accountSid, opts.authToken, { timeout: 3e4 });
62
62
  const from = opts.from.startsWith("whatsapp:") ? opts.from : `whatsapp:${opts.from}`;
63
63
  const to = opts.to.startsWith("whatsapp:") ? opts.to : `whatsapp:${opts.to}`;
64
64
  const chunks = opts.body ? splitMessage(opts.body) : [void 0];
65
65
  let lastSid = "";
66
- for (const chunk of chunks) {
67
- const msg = await client.messages.create({
68
- from,
69
- to,
70
- ...chunk ? { body: chunk } : {},
71
- ...opts.mediaUrl ? { mediaUrl: [opts.mediaUrl] } : {}
72
- });
73
- lastSid = msg.sid;
66
+ for (let i = 0; i < chunks.length; i += 1) {
67
+ const chunk = chunks[i];
68
+ console.log(
69
+ `[twilio-whatsapp] Twilio create start from=${from} to=${to} chunk=${i + 1}/${chunks.length} bodyChars=${chunk?.length ?? 0} media=${opts.mediaUrl ? "yes" : "no"}`
70
+ );
71
+ try {
72
+ const msg = await client.messages.create({
73
+ from,
74
+ to,
75
+ ...chunk ? { body: chunk } : {},
76
+ ...opts.mediaUrl ? { mediaUrl: [opts.mediaUrl] } : {}
77
+ });
78
+ lastSid = msg.sid;
79
+ console.log(
80
+ `[twilio-whatsapp] Twilio create ok sid=${msg.sid} status=${msg.status} chunk=${i + 1}/${chunks.length}`
81
+ );
82
+ } catch (err) {
83
+ console.error(
84
+ `[twilio-whatsapp] Twilio create failed chunk=${i + 1}/${chunks.length}:`,
85
+ err
86
+ );
87
+ throw err;
88
+ }
74
89
  }
75
90
  return lastSid;
76
91
  }
77
92
 
78
93
  // src/outbound-adapter.ts
94
+ function summarizeText(text) {
95
+ if (!text) return "chars=0";
96
+ return `chars=${text.length} preview=${JSON.stringify(text.slice(0, 120))}`;
97
+ }
79
98
  async function deliver(ctx) {
80
99
  const account = resolveTwilioAccount(ctx.cfg, ctx.accountId);
81
- const sid = await sendTwilioWhatsappMessage({
82
- accountSid: account.accountSid,
83
- authToken: account.authToken,
84
- from: account.phoneNumber,
85
- to: ctx.to,
86
- body: ctx.text || void 0,
87
- mediaUrl: ctx.mediaUrl
88
- });
89
- return sid;
100
+ console.log(
101
+ `[twilio-whatsapp] Durable adapter deliver start accountId=${ctx.accountId ?? "default"} from=${account.phoneNumber} to=${ctx.to} ${summarizeText(ctx.text)} media=${ctx.mediaUrl ? "yes" : "no"}`
102
+ );
103
+ try {
104
+ const sid = await sendTwilioWhatsappMessage({
105
+ accountSid: account.accountSid,
106
+ authToken: account.authToken,
107
+ from: account.phoneNumber,
108
+ to: ctx.to,
109
+ body: ctx.text || void 0,
110
+ mediaUrl: ctx.mediaUrl
111
+ });
112
+ console.log(
113
+ `[twilio-whatsapp] Durable adapter deliver ok accountId=${ctx.accountId ?? "default"} outboundSid=${sid}`
114
+ );
115
+ return sid;
116
+ } catch (err) {
117
+ console.error(
118
+ `[twilio-whatsapp] Durable adapter deliver failed accountId=${ctx.accountId ?? "default"}:`,
119
+ err
120
+ );
121
+ throw err;
122
+ }
90
123
  }
91
124
  var twilioWhatsappOutbound = {
92
125
  deliveryMode: "direct",
@@ -278,6 +311,14 @@ function startTypingKeepalive(opts) {
278
311
 
279
312
  // src/http-routes.ts
280
313
  var MEDIA_MAX_BYTES = 16 * 1024 * 1024;
314
+ function resolveDeliveryMode() {
315
+ const value = process.env.TWILIO_WHATSAPP_DELIVERY_MODE?.trim().toLowerCase();
316
+ return value === "durable" ? "durable" : "direct";
317
+ }
318
+ function summarizeText2(text) {
319
+ if (!text) return "chars=0";
320
+ return `chars=${text.length} preview=${JSON.stringify(text.slice(0, 120))}`;
321
+ }
281
322
  async function readBody(req) {
282
323
  return new Promise((resolve, reject) => {
283
324
  const chunks = [];
@@ -333,6 +374,9 @@ function registerTwilioWhatsappHttpRoutes(api) {
333
374
  const webhookUrl = String(headers["x-twilio-webhook-url"] ?? "");
334
375
  const fields = parseTwilioWebhook(rawBody);
335
376
  const toPhone = normalizePhoneNumber(fields.to);
377
+ console.log(
378
+ `[twilio-whatsapp] Webhook received sid=${fields.messageSid || "unknown"} from=${fields.from} to=${fields.to} bodyChars=${fields.body.length} media=${fields.media.length}`
379
+ );
336
380
  const stored = getRuntimeForPhoneNumber(toPhone);
337
381
  if (!stored) {
338
382
  console.error(`[twilio-whatsapp] No runtime found for To=${fields.to}`);
@@ -371,6 +415,9 @@ function registerTwilioWhatsappHttpRoutes(api) {
371
415
  return true;
372
416
  }
373
417
  twimlOk(res);
418
+ console.log(
419
+ `[twilio-whatsapp] Webhook acknowledged sid=${fields.messageSid || "unknown"} accountId=${account.accountId}`
420
+ );
374
421
  const resolvedMedia = [];
375
422
  if (hasMedia) {
376
423
  const results = await Promise.all(
@@ -379,6 +426,9 @@ function registerTwilioWhatsappHttpRoutes(api) {
379
426
  for (const r of results) {
380
427
  if (r) resolvedMedia.push(r);
381
428
  }
429
+ console.log(
430
+ `[twilio-whatsapp] Media resolved sid=${fields.messageSid || "unknown"} requested=${fields.media.length} saved=${resolvedMedia.length}`
431
+ );
382
432
  }
383
433
  const mediaContext = resolvedMedia.length > 0 ? {
384
434
  MediaPath: resolvedMedia[0].path,
@@ -440,18 +490,25 @@ ${text}`;
440
490
  bodyForAgent = messageText || "<media:audio>";
441
491
  }
442
492
  }
443
- console.log("[twilio-whatsapp] Dispatching bodyForAgent:", bodyForAgent);
444
- const stopTypingKeepalive = fields.messageSid ? startTypingKeepalive({
445
- accountSid: account.accountSid,
446
- authToken: account.authToken,
447
- messageId: fields.messageSid
448
- }) : () => {
493
+ const deliveryMode = resolveDeliveryMode();
494
+ console.log(
495
+ `[twilio-whatsapp] Dispatching inbound sid=${fields.messageSid || "unknown"} deliveryMode=${deliveryMode} bodyForAgent=${summarizeText2(bodyForAgent)}`
496
+ );
497
+ const stopTypingKeepalive = fields.messageSid ? (() => {
498
+ console.log(`[twilio-whatsapp] Typing keepalive start sid=${fields.messageSid}`);
499
+ return startTypingKeepalive({
500
+ accountSid: account.accountSid,
501
+ authToken: account.authToken,
502
+ messageId: fields.messageSid
503
+ });
504
+ })() : () => {
449
505
  };
450
506
  let typingStopped = false;
451
507
  const stopTyping = () => {
452
508
  if (typingStopped) return;
453
509
  typingStopped = true;
454
510
  stopTypingKeepalive();
511
+ console.log(`[twilio-whatsapp] Typing keepalive stop sid=${fields.messageSid || "unknown"}`);
455
512
  };
456
513
  const { route, buildEnvelope } = resolveInboundRouteEnvelopeBuilderWithRuntime({
457
514
  cfg: stored.cfg,
@@ -491,6 +548,9 @@ ${text}`;
491
548
  OriginatingTo: senderAddress,
492
549
  ...mediaContext
493
550
  });
551
+ console.log(
552
+ `[twilio-whatsapp] Route resolved sid=${fields.messageSid || "unknown"} agentId=${route.agentId} sessionKey=${route.sessionKey} accountId=${route.accountId ?? account.accountId}`
553
+ );
494
554
  stored.channelRuntime.inbound.dispatchReply({
495
555
  cfg: stored.cfg,
496
556
  channel: "twilio-whatsapp",
@@ -502,29 +562,57 @@ ${text}`;
502
562
  recordInboundSession: stored.channelRuntime.session.recordInboundSession,
503
563
  dispatchReplyWithBufferedBlockDispatcher: stored.channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher,
504
564
  delivery: {
505
- durable: () => ({
506
- to: senderAddress
507
- }),
565
+ ...deliveryMode === "durable" ? {
566
+ durable: {
567
+ replyToMode: "first"
568
+ }
569
+ } : {},
508
570
  deliver: async (payload) => {
509
571
  const outbound = payload;
510
572
  const text = outbound.text ?? "";
511
573
  const mediaUrl = outbound.mediaUrl ?? outbound.mediaUrls?.[0];
574
+ console.log(
575
+ `[twilio-whatsapp] Direct fallback deliver invoked sid=${fields.messageSid || "unknown"} deliveryMode=${deliveryMode} ${summarizeText2(text)} media=${mediaUrl ? "yes" : "no"}`
576
+ );
512
577
  if (!text && !mediaUrl) {
578
+ console.log(
579
+ `[twilio-whatsapp] Direct fallback skipped empty payload sid=${fields.messageSid || "unknown"}`
580
+ );
513
581
  return { visibleReplySent: false };
514
582
  }
515
- const messageId = await sendTwilioWhatsappMessage({
516
- accountSid: account.accountSid,
517
- authToken: account.authToken,
518
- from: toPhone,
519
- to: fromPhone,
520
- body: text || void 0,
521
- mediaUrl
522
- });
583
+ let messageId;
584
+ try {
585
+ console.log(
586
+ `[twilio-whatsapp] Twilio direct send start sid=${fields.messageSid || "unknown"} from=${recipientAddress} to=${senderAddress} ${summarizeText2(text)} media=${mediaUrl ? "yes" : "no"}`
587
+ );
588
+ messageId = await sendTwilioWhatsappMessage({
589
+ accountSid: account.accountSid,
590
+ authToken: account.authToken,
591
+ from: toPhone,
592
+ to: fromPhone,
593
+ body: text || void 0,
594
+ mediaUrl
595
+ });
596
+ console.log(
597
+ `[twilio-whatsapp] Twilio direct send ok sid=${fields.messageSid || "unknown"} outboundSid=${messageId}`
598
+ );
599
+ } catch (err) {
600
+ console.error(
601
+ `[twilio-whatsapp] Twilio direct send failed sid=${fields.messageSid || "unknown"}:`,
602
+ err
603
+ );
604
+ throw err;
605
+ }
523
606
  return {
524
607
  messageIds: [messageId],
525
608
  visibleReplySent: true
526
609
  };
527
610
  },
611
+ onDelivered: (_payload, info, result) => {
612
+ console.log(
613
+ `[twilio-whatsapp] Delivery observer sid=${fields.messageSid || "unknown"} kind=${info.kind} result=${JSON.stringify(result)}`
614
+ );
615
+ },
528
616
  onError: (err, info) => {
529
617
  stopTyping();
530
618
  console.error(`[twilio-whatsapp] Dispatch error (${info.kind}):`, err);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daolmedo/openclaw-twilio-whatsapp",
3
- "version": "1.1.21",
3
+ "version": "1.1.23",
4
4
  "description": "Twilio WhatsApp channel plugin for OpenClaw (Cody ISV)",
5
5
  "type": "module",
6
6
  "exports": {