@daolmedo/openclaw-twilio-whatsapp 1.1.20 → 1.1.22

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 +101 -36
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -38,6 +38,9 @@ function normalizePhoneNumber(phone) {
38
38
  return phone.replace(/^whatsapp:/, "").replace(/\s+/g, "");
39
39
  }
40
40
 
41
+ // src/outbound-adapter.ts
42
+ import { createChannelMessageAdapterFromOutbound } from "openclaw/plugin-sdk/channel-outbound";
43
+
41
44
  // src/send.ts
42
45
  import twilio from "twilio";
43
46
  var MAX_BODY_LENGTH = 1550;
@@ -55,7 +58,7 @@ function splitMessage(text) {
55
58
  return chunks;
56
59
  }
57
60
  async function sendTwilioWhatsappMessage(opts) {
58
- const client = twilio(opts.accountSid, opts.authToken);
61
+ const client = twilio(opts.accountSid, opts.authToken, { timeout: 3e4 });
59
62
  const from = opts.from.startsWith("whatsapp:") ? opts.from : `whatsapp:${opts.from}`;
60
63
  const to = opts.to.startsWith("whatsapp:") ? opts.to : `whatsapp:${opts.to}`;
61
64
  const chunks = opts.body ? splitMessage(opts.body) : [void 0];
@@ -87,6 +90,13 @@ async function deliver(ctx) {
87
90
  }
88
91
  var twilioWhatsappOutbound = {
89
92
  deliveryMode: "direct",
93
+ deliveryCapabilities: {
94
+ durableFinal: {
95
+ text: true,
96
+ media: true,
97
+ messageSendingHooks: true
98
+ }
99
+ },
90
100
  async sendText(ctx) {
91
101
  const messageId = await deliver(ctx);
92
102
  return {
@@ -102,6 +112,10 @@ var twilioWhatsappOutbound = {
102
112
  };
103
113
  }
104
114
  };
115
+ var twilioWhatsappMessageAdapter = createChannelMessageAdapterFromOutbound({
116
+ id: "twilio-whatsapp",
117
+ outbound: twilioWhatsappOutbound
118
+ });
105
119
 
106
120
  // src/runtime-store.ts
107
121
  var phoneToRuntime = /* @__PURE__ */ new Map();
@@ -152,6 +166,7 @@ var twilioWhatsappPlugin = {
152
166
  }
153
167
  },
154
168
  outbound: twilioWhatsappOutbound,
169
+ message: twilioWhatsappMessageAdapter,
155
170
  gateway: {
156
171
  async startAccount(ctx) {
157
172
  const account = ctx.account;
@@ -196,7 +211,7 @@ var twilioWhatsappPlugin = {
196
211
  // src/http-routes.ts
197
212
  import { readFile } from "node:fs/promises";
198
213
  import path from "node:path";
199
- import { dispatchInboundDirectDmWithRuntime } from "openclaw/plugin-sdk/direct-dm";
214
+ import { resolveInboundRouteEnvelopeBuilderWithRuntime } from "openclaw/plugin-sdk/inbound-envelope";
200
215
  import { saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
201
216
 
202
217
  // src/webhook.ts
@@ -426,53 +441,103 @@ ${text}`;
426
441
  }
427
442
  }
428
443
  console.log("[twilio-whatsapp] Dispatching bodyForAgent:", bodyForAgent);
429
- const stopTyping = fields.messageSid ? startTypingKeepalive({
444
+ const stopTypingKeepalive = fields.messageSid ? startTypingKeepalive({
430
445
  accountSid: account.accountSid,
431
446
  authToken: account.authToken,
432
447
  messageId: fields.messageSid
433
448
  }) : () => {
434
449
  };
435
- dispatchInboundDirectDmWithRuntime({
450
+ let typingStopped = false;
451
+ const stopTyping = () => {
452
+ if (typingStopped) return;
453
+ typingStopped = true;
454
+ stopTypingKeepalive();
455
+ };
456
+ const { route, buildEnvelope } = resolveInboundRouteEnvelopeBuilderWithRuntime({
436
457
  cfg: stored.cfg,
437
- runtime: { channel: stored.channelRuntime },
438
458
  channel: "twilio-whatsapp",
439
- channelLabel: "WhatsApp",
440
459
  accountId: account.accountId,
441
460
  peer: { kind: "direct", id: fromPhone },
442
- senderId: fromPhone,
443
- senderAddress,
444
- recipientAddress,
445
- conversationLabel,
446
- rawBody: mediaContext.Transcript ? String(mediaContext.Transcript) : messageText,
447
- bodyForAgent,
448
- messageId: fields.messageSid,
449
- timestamp: Date.now(),
450
- commandAuthorized: true,
451
- extraContext: mediaContext,
452
- deliver: async (payload) => {
453
- stopTyping();
454
- const text = payload.text ?? "";
455
- const mediaUrl = payload.mediaUrl ?? payload.mediaUrls?.[0];
456
- if (!text && !mediaUrl) return;
457
- await sendTwilioWhatsappMessage({
458
- accountSid: account.accountSid,
459
- authToken: account.authToken,
460
- from: toPhone,
461
- to: fromPhone,
462
- body: text || void 0,
463
- mediaUrl
464
- });
461
+ runtime: stored.channelRuntime,
462
+ sessionStore: stored.cfg.session?.store
463
+ });
464
+ const timestamp = Date.now();
465
+ const rawBodyForContext = mediaContext.Transcript ? String(mediaContext.Transcript) : messageText;
466
+ const { storePath, body } = buildEnvelope({
467
+ channel: "WhatsApp",
468
+ from: conversationLabel,
469
+ body: rawBodyForContext,
470
+ timestamp
471
+ });
472
+ const ctxPayload = stored.channelRuntime.reply.finalizeInboundContext({
473
+ Body: body,
474
+ BodyForAgent: bodyForAgent ?? rawBodyForContext,
475
+ RawBody: rawBodyForContext,
476
+ CommandBody: rawBodyForContext,
477
+ From: senderAddress,
478
+ To: recipientAddress,
479
+ SessionKey: route.sessionKey,
480
+ AccountId: route.accountId ?? account.accountId,
481
+ ChatType: "direct",
482
+ ConversationLabel: conversationLabel,
483
+ SenderId: fromPhone,
484
+ Provider: "twilio-whatsapp",
485
+ Surface: "twilio-whatsapp",
486
+ MessageSid: fields.messageSid,
487
+ MessageSidFull: fields.messageSid,
488
+ Timestamp: timestamp,
489
+ CommandAuthorized: true,
490
+ OriginatingChannel: "twilio-whatsapp",
491
+ OriginatingTo: senderAddress,
492
+ ...mediaContext
493
+ });
494
+ stored.channelRuntime.inbound.dispatchReply({
495
+ cfg: stored.cfg,
496
+ channel: "twilio-whatsapp",
497
+ accountId: route.accountId ?? account.accountId,
498
+ agentId: route.agentId,
499
+ routeSessionKey: route.sessionKey,
500
+ storePath,
501
+ ctxPayload,
502
+ recordInboundSession: stored.channelRuntime.session.recordInboundSession,
503
+ dispatchReplyWithBufferedBlockDispatcher: stored.channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher,
504
+ delivery: {
505
+ deliver: async (payload) => {
506
+ const outbound = payload;
507
+ const text = outbound.text ?? "";
508
+ const mediaUrl = outbound.mediaUrl ?? outbound.mediaUrls?.[0];
509
+ if (!text && !mediaUrl) {
510
+ return { visibleReplySent: false };
511
+ }
512
+ const messageId = await sendTwilioWhatsappMessage({
513
+ accountSid: account.accountSid,
514
+ authToken: account.authToken,
515
+ from: toPhone,
516
+ to: fromPhone,
517
+ body: text || void 0,
518
+ mediaUrl
519
+ });
520
+ return {
521
+ messageIds: [messageId],
522
+ visibleReplySent: true
523
+ };
524
+ },
525
+ onError: (err, info) => {
526
+ stopTyping();
527
+ console.error(`[twilio-whatsapp] Dispatch error (${info.kind}):`, err);
528
+ }
465
529
  },
466
- onRecordError: (err) => {
467
- console.error("[twilio-whatsapp] Session record error:", err);
530
+ replyPipeline: {},
531
+ record: {
532
+ onRecordError: (err) => {
533
+ console.error("[twilio-whatsapp] Session record error:", err);
534
+ }
468
535
  },
469
- onDispatchError: (err, info) => {
470
- stopTyping();
471
- console.error(`[twilio-whatsapp] Dispatch error (${info.kind}):`, err);
472
- }
536
+ messageId: fields.messageSid
473
537
  }).catch((err) => {
474
- stopTyping();
475
538
  console.error("[twilio-whatsapp] Unhandled dispatch error:", err);
539
+ }).finally(() => {
540
+ stopTyping();
476
541
  });
477
542
  return true;
478
543
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@daolmedo/openclaw-twilio-whatsapp",
3
- "version": "1.1.20",
3
+ "version": "1.1.22",
4
4
  "description": "Twilio WhatsApp channel plugin for OpenClaw (Cody ISV)",
5
5
  "type": "module",
6
6
  "exports": {