@0xmaxma/claude-gateway 1.8.10 → 1.8.12

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 (74) hide show
  1. package/README.md +14 -1
  2. package/config.template.json +2 -1
  3. package/dist/agent/runner.d.ts +72 -0
  4. package/dist/agent/runner.d.ts.map +1 -1
  5. package/dist/agent/runner.js +300 -6
  6. package/dist/agent/runner.js.map +1 -1
  7. package/dist/api/router.d.ts.map +1 -1
  8. package/dist/api/router.js +1001 -4
  9. package/dist/api/router.js.map +1 -1
  10. package/dist/api/webhooks-router.d.ts +2 -0
  11. package/dist/api/webhooks-router.d.ts.map +1 -1
  12. package/dist/api/webhooks-router.js +2 -0
  13. package/dist/api/webhooks-router.js.map +1 -1
  14. package/dist/api/whatsapp-access.d.ts +123 -0
  15. package/dist/api/whatsapp-access.d.ts.map +1 -0
  16. package/dist/api/whatsapp-access.js +135 -0
  17. package/dist/api/whatsapp-access.js.map +1 -0
  18. package/dist/api/whatsapp-cloud-access.d.ts +48 -0
  19. package/dist/api/whatsapp-cloud-access.d.ts.map +1 -0
  20. package/dist/api/whatsapp-cloud-access.js +58 -0
  21. package/dist/api/whatsapp-cloud-access.js.map +1 -0
  22. package/dist/api/whatsapp-cloud-client.d.ts +143 -0
  23. package/dist/api/whatsapp-cloud-client.d.ts.map +1 -0
  24. package/dist/api/whatsapp-cloud-client.js +298 -0
  25. package/dist/api/whatsapp-cloud-client.js.map +1 -0
  26. package/dist/api/whatsapp-cloud-webhook-router.d.ts +132 -0
  27. package/dist/api/whatsapp-cloud-webhook-router.d.ts.map +1 -0
  28. package/dist/api/whatsapp-cloud-webhook-router.js +494 -0
  29. package/dist/api/whatsapp-cloud-webhook-router.js.map +1 -0
  30. package/dist/config/loader.d.ts.map +1 -1
  31. package/dist/config/loader.js +7 -0
  32. package/dist/config/loader.js.map +1 -1
  33. package/dist/config/whatsapp-accounts.d.ts +77 -0
  34. package/dist/config/whatsapp-accounts.d.ts.map +1 -0
  35. package/dist/config/whatsapp-accounts.js +218 -0
  36. package/dist/config/whatsapp-accounts.js.map +1 -0
  37. package/dist/history/db.d.ts.map +1 -1
  38. package/dist/history/db.js +43 -11
  39. package/dist/history/db.js.map +1 -1
  40. package/dist/history/types.d.ts +18 -1
  41. package/dist/history/types.d.ts.map +1 -1
  42. package/dist/history/types.js +1 -1
  43. package/dist/history/types.js.map +1 -1
  44. package/dist/index.js +18 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/session/process.d.ts.map +1 -1
  47. package/dist/session/process.js +37 -0
  48. package/dist/session/process.js.map +1 -1
  49. package/dist/shared/image-optimize.d.ts +32 -0
  50. package/dist/shared/image-optimize.d.ts.map +1 -0
  51. package/dist/shared/image-optimize.js +197 -0
  52. package/dist/shared/image-optimize.js.map +1 -0
  53. package/dist/shared/image-sniff.d.ts +9 -0
  54. package/dist/shared/image-sniff.d.ts.map +1 -1
  55. package/dist/shared/image-sniff.js +13 -0
  56. package/dist/shared/image-sniff.js.map +1 -1
  57. package/dist/shared/text-chunk.d.ts +37 -0
  58. package/dist/shared/text-chunk.d.ts.map +1 -0
  59. package/dist/shared/text-chunk.js +105 -0
  60. package/dist/shared/text-chunk.js.map +1 -0
  61. package/dist/shared/whatsapp-ack.d.ts +13 -0
  62. package/dist/shared/whatsapp-ack.d.ts.map +1 -0
  63. package/dist/shared/whatsapp-ack.js +16 -0
  64. package/dist/shared/whatsapp-ack.js.map +1 -0
  65. package/dist/types.d.ts +158 -0
  66. package/dist/types.d.ts.map +1 -1
  67. package/dist/whatsapp/manager.d.ts +173 -0
  68. package/dist/whatsapp/manager.d.ts.map +1 -0
  69. package/dist/whatsapp/manager.js +838 -0
  70. package/dist/whatsapp/manager.js.map +1 -0
  71. package/mcp/server.ts +4 -0
  72. package/mcp/tools/whatsapp/module.ts +162 -0
  73. package/mcp/tools/whatsapp-cloud/module.ts +536 -0
  74. package/package.json +9 -4
@@ -22,6 +22,7 @@ import { Router, type Request, type Response } from 'express';
22
22
  import type { AgentRunner } from '../agent/runner';
23
23
  import { type LineWebhookOptions } from './line-webhook-router';
24
24
  import { type SlackWebhookOptions } from './slack-webhook-router';
25
+ import { type WhatsAppCloudWebhookOptions } from './whatsapp-cloud-webhook-router';
25
26
  /**
26
27
  * A per-app webhook handler. `verify` answers provider URL-verification probes
27
28
  * (GET / empty POST); `handlePost` processes a signed inbound POST. Each handler
@@ -40,6 +41,7 @@ export interface WebhookAppHandler {
40
41
  export interface WebhooksOptions {
41
42
  line?: LineWebhookOptions;
42
43
  slack?: SlackWebhookOptions;
44
+ whatsapp_cloud?: WhatsAppCloudWebhookOptions;
43
45
  }
44
46
  export declare function createWebhooksRouter(agents: Map<string, AgentRunner>, logDir: string, opts?: WebhooksOptions): Router;
45
47
  //# sourceMappingURL=webhooks-router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks-router.d.ts","sourceRoot":"","sources":["../../src/api/webhooks-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAgB,EAAE,MAAM,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAI7F;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAC1B,KAAK,CAAC,EAAE,mBAAmB,CAAC;CAC7B;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EAChC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,eAAoB,GACzB,MAAM,CAsCR"}
1
+ {"version":3,"file":"webhooks-router.d.ts","sourceRoot":"","sources":["../../src/api/webhooks-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAgB,EAAE,MAAM,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC1F,OAAO,EAA6B,KAAK,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7F,OAAO,EAAqC,KAAK,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAItH;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1C,UAAU,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxD;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,kBAAkB,CAAC;IAC1B,KAAK,CAAC,EAAE,mBAAmB,CAAC;IAC5B,cAAc,CAAC,EAAE,2BAA2B,CAAC;CAC9C;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,EAChC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,eAAoB,GACzB,MAAM,CAuCR"}
@@ -57,6 +57,7 @@ exports.createWebhooksRouter = createWebhooksRouter;
57
57
  const express_1 = __importStar(require("express"));
58
58
  const line_webhook_router_1 = require("./line-webhook-router");
59
59
  const slack_webhook_router_1 = require("./slack-webhook-router");
60
+ const whatsapp_cloud_webhook_router_1 = require("./whatsapp-cloud-webhook-router");
60
61
  const MAX_BODY_BYTES = 256 * 1024; // pre-auth body cap
61
62
  function createWebhooksRouter(agents, logDir, opts = {}) {
62
63
  const router = (0, express_1.Router)();
@@ -64,6 +65,7 @@ function createWebhooksRouter(agents, logDir, opts = {}) {
64
65
  const handlers = {
65
66
  line: (0, line_webhook_router_1.createLineWebhookHandler)(agents, logDir, opts.line ?? {}),
66
67
  slack: (0, slack_webhook_router_1.createSlackWebhookHandler)(agents, logDir, opts.slack ?? {}),
68
+ whatsapp_cloud: (0, whatsapp_cloud_webhook_router_1.createWhatsAppCloudWebhookHandler)(agents, logDir, opts.whatsapp_cloud ?? {}),
67
69
  };
68
70
  const resolve = (req, res) => {
69
71
  const app = req.params.app;
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks-router.js","sourceRoot":"","sources":["../../src/api/webhooks-router.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,oDA0CC;AA1FD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,mDAAuE;AAEvE,+DAA0F;AAC1F,iEAA6F;AAE7F,MAAM,cAAc,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,oBAAoB;AAuBvD,SAAgB,oBAAoB,CAClC,MAAgC,EAChC,MAAc,EACd,OAAwB,EAAE;IAE1B,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;IACxB,MAAM,OAAO,GAAG,iBAAO,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;IAEpE,MAAM,QAAQ,GAAsC;QAClD,IAAI,EAAE,IAAA,8CAAwB,EAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QAC/D,KAAK,EAAE,IAAA,gDAAyB,EAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;KACnE,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,GAAY,EAAE,GAAa,EAA4B,EAAE;QACxE,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;QAC3B,+EAA+E;QAC/E,oFAAoF;QACpF,kFAAkF;QAClF,yCAAyC;QACzC,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAChG,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wBAAwB,GAAG,EAAE,EAAE,CAAC,CAAC;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,GAAY,EAAE,GAAa,EAAQ,EAAE;QACxD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAClC,IAAI,OAAO;YAAE,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACxC,CAAC,CAAC;IACF,MAAM,YAAY,GAAG,CAAC,GAAY,EAAE,GAAa,EAAQ,EAAE;QACzD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAClC,IAAI,OAAO;YAAE,KAAK,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACjD,CAAC,CAAC;IAEF,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACjC,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;IAC1C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;IAC5C,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;IAErD,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"webhooks-router.js","sourceRoot":"","sources":["../../src/api/webhooks-router.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,oDA2CC;AA7FD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,mDAAuE;AAEvE,+DAA0F;AAC1F,iEAA6F;AAC7F,mFAAsH;AAEtH,MAAM,cAAc,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,oBAAoB;AAwBvD,SAAgB,oBAAoB,CAClC,MAAgC,EAChC,MAAc,EACd,OAAwB,EAAE;IAE1B,MAAM,MAAM,GAAG,IAAA,gBAAM,GAAE,CAAC;IACxB,MAAM,OAAO,GAAG,iBAAO,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;IAEpE,MAAM,QAAQ,GAAsC;QAClD,IAAI,EAAE,IAAA,8CAAwB,EAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;QAC/D,KAAK,EAAE,IAAA,gDAAyB,EAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;QAClE,cAAc,EAAE,IAAA,iEAAiC,EAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,cAAc,IAAI,EAAE,CAAC;KAC7F,CAAC;IAEF,MAAM,OAAO,GAAG,CAAC,GAAY,EAAE,GAAa,EAA4B,EAAE;QACxE,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;QAC3B,+EAA+E;QAC/E,oFAAoF;QACpF,kFAAkF;QAClF,yCAAyC;QACzC,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAChG,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,wBAAwB,GAAG,EAAE,EAAE,CAAC,CAAC;YAC/D,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,GAAY,EAAE,GAAa,EAAQ,EAAE;QACxD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAClC,IAAI,OAAO;YAAE,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACxC,CAAC,CAAC;IACF,MAAM,YAAY,GAAG,CAAC,GAAY,EAAE,GAAa,EAAQ,EAAE;QACzD,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAClC,IAAI,OAAO;YAAE,KAAK,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IACjD,CAAC,CAAC;IAEF,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACjC,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,WAAW,CAAC,CAAC;IAC1C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;IAC5C,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC;IAErD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * WhatsApp DM/group access control — pure, stateless sender gate.
3
+ *
4
+ * Structural port of `slack-access.ts` (same closed-by-default posture,
5
+ * same two-tier dmPolicy/groupPolicy shape — Slack is the right template
6
+ * here, not the DM-only `sms-access.ts`, because WhatsApp has real groups).
7
+ * The manager reads `whatsapp` config directly and calls this once per
8
+ * inbound message — no env plumbing, no state files.
9
+ *
10
+ * Default (policy absent) is CLOSED: only ids in the allowlist pass. Set
11
+ * `dmPolicy`/`groupPolicy`: 'open' to restore "reply to anyone" behavior.
12
+ *
13
+ * Allowlist entries MUST be WhatsApp JIDs (e.g. "66812345678@s.whatsapp.net"
14
+ * for a DM sender, "1234567890-1234567890@g.us" for a group) — never a
15
+ * bare phone number or a group's display name (renamable/ambiguous, same
16
+ * footgun Slack/LINE's own doc comments call out).
17
+ */
18
+ export declare function isWhatsAppSenderAllowed(policy: 'open' | 'allowlist' | 'disabled' | undefined, allowlist: string[] | undefined, id: string): boolean;
19
+ /**
20
+ * Structural view of a Baileys inbound message — the only fields the gate
21
+ * needs. Kept local (not the Baileys `WAMessage` type) so this module stays
22
+ * pure and trivially testable.
23
+ */
24
+ export interface WhatsAppMessageLike {
25
+ key?: {
26
+ remoteJid?: string | null;
27
+ /** Sender within a group (absent for 1:1 DMs, where remoteJid IS the sender). */
28
+ participant?: string | null;
29
+ /**
30
+ * Phone-number JID for `participant`, present when WhatsApp's Linked ID
31
+ * (LID) privacy system reports `participant` as a `@lid` address instead
32
+ * of the classic `@s.whatsapp.net` phone JID.
33
+ */
34
+ participantPn?: string | null;
35
+ /**
36
+ * Phone-number JID for `remoteJid` on a 1:1 chat, present when LID
37
+ * privacy reports `remoteJid` itself as `@lid` instead of the classic
38
+ * `@s.whatsapp.net` form.
39
+ */
40
+ senderPn?: string | null;
41
+ fromMe?: boolean | null;
42
+ } | null;
43
+ message?: {
44
+ /** @mentioned JIDs, present on group messages that tag someone (including the bot). */
45
+ extendedTextMessage?: {
46
+ contextInfo?: {
47
+ mentionedJid?: string[] | null;
48
+ } | null;
49
+ } | null;
50
+ imageMessage?: {
51
+ contextInfo?: {
52
+ mentionedJid?: string[] | null;
53
+ } | null;
54
+ } | null;
55
+ } | null;
56
+ }
57
+ export type WhatsAppSourceKind = 'user' | 'group' | 'other';
58
+ export interface ResolvedWhatsAppSource {
59
+ /** Conversation key — the reply target: the DM JID or the group JID. */
60
+ conversationId: string;
61
+ /** The human who sent the message — same as conversationId for a DM, the participant JID for a group. */
62
+ senderId: string;
63
+ kind: WhatsAppSourceKind;
64
+ /** @mentioned JIDs on this message (group messages only; empty for DMs). */
65
+ mentionedJids: string[];
66
+ }
67
+ /** Map a raw Baileys message to {conversationId, senderId, kind, mentionedJids}. */
68
+ export declare function resolveWhatsAppSource(msg: WhatsAppMessageLike | undefined | null): ResolvedWhatsAppSource;
69
+ /** The subset of `whatsapp` config the conversation gate reads. */
70
+ export interface WhatsAppAccessConfig {
71
+ dmPolicy?: 'open' | 'allowlist' | 'disabled';
72
+ dmAllowlist?: string[];
73
+ groupPolicy?: 'open' | 'allowlist' | 'disabled';
74
+ groupAllowlist?: string[];
75
+ }
76
+ /**
77
+ * Gate an already-resolved WhatsApp source. DMs are gated on the sender's
78
+ * JID against `dmPolicy`/`dmAllowlist`; groups are gated on the group JID
79
+ * against `groupPolicy`/`groupAllowlist`, closed by default — same posture
80
+ * as DMs. Unknown source kinds are denied.
81
+ */
82
+ export declare function isResolvedSourceAllowed(cfg: WhatsAppAccessConfig | undefined, resolved: ResolvedWhatsAppSource): boolean;
83
+ /**
84
+ * Convenience wrapper that resolves a raw message and gates it in one call.
85
+ * Equivalent to `isResolvedSourceAllowed(cfg, resolveWhatsAppSource(msg))`.
86
+ */
87
+ export declare function isWhatsAppConversationAllowed(cfg: WhatsAppAccessConfig | undefined, msg: WhatsAppMessageLike | undefined | null): boolean;
88
+ /**
89
+ * @mention gate for groups (mirrors `slack`/`line`'s `requireMention`,
90
+ * default true — only effective in groups, DMs always pass). Unlike Slack
91
+ * (which infers "was mentioned" from the event *type* being `app_mention`)
92
+ * Baileys hands every message's mentioned-JID list directly on
93
+ * `contextInfo.mentionedJid` — no separate text-scanning heuristic needed,
94
+ * just check one of the bot's own JIDs is in that list.
95
+ *
96
+ * Under WhatsApp's Linked ID (LID) privacy system a group can report the
97
+ * mentioned bot using its `@lid` identity even though `sock.user.id` (the
98
+ * phone-number JID we log in with) is the classic `@s.whatsapp.net` form —
99
+ * two different strings for the same bot. Pass `botLid` (`sock.user.lid`)
100
+ * alongside `botJid` (`sock.user.id`) so either form matches; omit it if
101
+ * unknown (older Baileys / non-LID accounts) and only `botJid` is checked.
102
+ */
103
+ export declare function wasBotMentioned(mentionedJids: string[], botJid: string | undefined, botLid?: string | null): boolean;
104
+ /**
105
+ * Canonical form of a WhatsApp JID for equality checks.
106
+ *
107
+ * WhatsApp JIDs occasionally carry a ":<device>" suffix on the *user* part
108
+ * (multi-device, e.g. "66811112222:5@s.whatsapp.net") — strip only that
109
+ * segment, not everything after the first colon, or the "@server" half gets
110
+ * dropped too and a same-user-different-device JID never matches at all
111
+ * (splitting on ':' before splitting on '@' would do exactly that). The
112
+ * `@lid` vs `@s.whatsapp.net` distinction is preserved: those are genuinely
113
+ * different addresses, and callers that must accept either (wasBotMentioned)
114
+ * normalize both forms and compare against the set.
115
+ *
116
+ * Shared by the INBOUND mention gate (wasBotMentioned above) and the OUTBOUND
117
+ * `@digits` → `mentions:[jid]` matcher in WhatsAppManager, so both sides agree
118
+ * on what "the same participant" means.
119
+ */
120
+ export declare function normalizeWhatsAppJid(jid: string): string;
121
+ /** The user half of a normalized JID — "66811112222" from "66811112222:5@s.whatsapp.net". */
122
+ export declare function whatsAppJidUser(jid: string): string;
123
+ //# sourceMappingURL=whatsapp-access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whatsapp-access.d.ts","sourceRoot":"","sources":["../../src/api/whatsapp-access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,EACrD,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,EAC/B,EAAE,EAAE,MAAM,GACT,OAAO,CAKT;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE;QACJ,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,iFAAiF;QACjF,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B;;;;WAIG;QACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B;;;;WAIG;QACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;KACzB,GAAG,IAAI,CAAC;IACT,OAAO,CAAC,EAAE;QACR,uFAAuF;QACvF,mBAAmB,CAAC,EAAE;YAAE,WAAW,CAAC,EAAE;gBAAE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;aAAE,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAC;QACzF,YAAY,CAAC,EAAE;YAAE,WAAW,CAAC,EAAE;gBAAE,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;aAAE,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAC;KACnF,GAAG,IAAI,CAAC;CACV;AAED,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAE5D,MAAM,WAAW,sBAAsB;IACrC,wEAAwE;IACxE,cAAc,EAAE,MAAM,CAAC;IACvB,yGAAyG;IACzG,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,kBAAkB,CAAC;IACzB,4EAA4E;IAC5E,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,oFAAoF;AACpF,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,mBAAmB,GAAG,SAAS,GAAG,IAAI,GAAG,sBAAsB,CA+BzG;AAED,mEAAmE;AACnE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAChD,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,oBAAoB,GAAG,SAAS,EACrC,QAAQ,EAAE,sBAAsB,GAC/B,OAAO,CAST;AAED;;;GAGG;AACH,wBAAgB,6BAA6B,CAC3C,GAAG,EAAE,oBAAoB,GAAG,SAAS,EACrC,GAAG,EAAE,mBAAmB,GAAG,SAAS,GAAG,IAAI,GAC1C,OAAO,CAET;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAC7B,aAAa,EAAE,MAAM,EAAE,EACvB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GACrB,OAAO,CAIT;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGxD;AAED,6FAA6F;AAC7F,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnD"}
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isWhatsAppSenderAllowed = isWhatsAppSenderAllowed;
4
+ exports.resolveWhatsAppSource = resolveWhatsAppSource;
5
+ exports.isResolvedSourceAllowed = isResolvedSourceAllowed;
6
+ exports.isWhatsAppConversationAllowed = isWhatsAppConversationAllowed;
7
+ exports.wasBotMentioned = wasBotMentioned;
8
+ exports.normalizeWhatsAppJid = normalizeWhatsAppJid;
9
+ exports.whatsAppJidUser = whatsAppJidUser;
10
+ /**
11
+ * WhatsApp DM/group access control — pure, stateless sender gate.
12
+ *
13
+ * Structural port of `slack-access.ts` (same closed-by-default posture,
14
+ * same two-tier dmPolicy/groupPolicy shape — Slack is the right template
15
+ * here, not the DM-only `sms-access.ts`, because WhatsApp has real groups).
16
+ * The manager reads `whatsapp` config directly and calls this once per
17
+ * inbound message — no env plumbing, no state files.
18
+ *
19
+ * Default (policy absent) is CLOSED: only ids in the allowlist pass. Set
20
+ * `dmPolicy`/`groupPolicy`: 'open' to restore "reply to anyone" behavior.
21
+ *
22
+ * Allowlist entries MUST be WhatsApp JIDs (e.g. "66812345678@s.whatsapp.net"
23
+ * for a DM sender, "1234567890-1234567890@g.us" for a group) — never a
24
+ * bare phone number or a group's display name (renamable/ambiguous, same
25
+ * footgun Slack/LINE's own doc comments call out).
26
+ */
27
+ function isWhatsAppSenderAllowed(policy, allowlist, id) {
28
+ if (policy === 'open')
29
+ return true;
30
+ if (policy === 'disabled')
31
+ return false;
32
+ // 'allowlist' OR undefined (closed default) → only listed ids pass.
33
+ return !!id && (allowlist ?? []).includes(id);
34
+ }
35
+ /** Map a raw Baileys message to {conversationId, senderId, kind, mentionedJids}. */
36
+ function resolveWhatsAppSource(msg) {
37
+ const remoteJid = msg?.key?.remoteJid ?? '';
38
+ if (!remoteJid)
39
+ return { conversationId: '', senderId: '', kind: 'other', mentionedJids: [] };
40
+ const mentionedJids = msg?.message?.extendedTextMessage?.contextInfo?.mentionedJid ??
41
+ msg?.message?.imageMessage?.contextInfo?.mentionedJid ??
42
+ [];
43
+ if (remoteJid.endsWith('@g.us')) {
44
+ // Group: the sender is `participant`, the conversation is the group JID itself.
45
+ // `participant` arrives as a `@lid` address (not the classic phone JID)
46
+ // for some senders under WhatsApp's Linked ID privacy system — prefer
47
+ // `participantPn` (the real phone-number JID) when Baileys supplies it.
48
+ const participant = msg?.key?.participantPn || msg?.key?.participant || '';
49
+ if (!participant)
50
+ return { conversationId: '', senderId: '', kind: 'other', mentionedJids: [] };
51
+ return { conversationId: remoteJid, senderId: participant, kind: 'group', mentionedJids };
52
+ }
53
+ if (remoteJid.endsWith('@s.whatsapp.net') || remoteJid.endsWith('@lid')) {
54
+ // DM: sender and conversation are (normally) the same JID. Under
55
+ // WhatsApp's Linked ID (LID) privacy system, `remoteJid` itself can
56
+ // arrive as `<id>@lid` instead of the classic `<phone>@s.whatsapp.net`
57
+ // — reply on whichever JID we actually received the message on
58
+ // (`remoteJid`), but gate on the real phone-number JID (`senderPn`, when
59
+ // Baileys supplies it): allowlists are documented and configured in
60
+ // phone-number JID form and would never match a bare `@lid`.
61
+ const senderId = msg?.key?.senderPn || remoteJid;
62
+ return { conversationId: remoteJid, senderId, kind: 'user', mentionedJids: [] };
63
+ }
64
+ // Broadcast lists, status updates, newsletters, etc. — not a supported source.
65
+ return { conversationId: '', senderId: '', kind: 'other', mentionedJids: [] };
66
+ }
67
+ /**
68
+ * Gate an already-resolved WhatsApp source. DMs are gated on the sender's
69
+ * JID against `dmPolicy`/`dmAllowlist`; groups are gated on the group JID
70
+ * against `groupPolicy`/`groupAllowlist`, closed by default — same posture
71
+ * as DMs. Unknown source kinds are denied.
72
+ */
73
+ function isResolvedSourceAllowed(cfg, resolved) {
74
+ const { conversationId, senderId, kind } = resolved;
75
+ if (kind === 'user') {
76
+ return isWhatsAppSenderAllowed(cfg?.dmPolicy, cfg?.dmAllowlist, senderId || conversationId);
77
+ }
78
+ if (kind === 'group') {
79
+ return isWhatsAppSenderAllowed(cfg?.groupPolicy, cfg?.groupAllowlist, conversationId);
80
+ }
81
+ return false;
82
+ }
83
+ /**
84
+ * Convenience wrapper that resolves a raw message and gates it in one call.
85
+ * Equivalent to `isResolvedSourceAllowed(cfg, resolveWhatsAppSource(msg))`.
86
+ */
87
+ function isWhatsAppConversationAllowed(cfg, msg) {
88
+ return isResolvedSourceAllowed(cfg, resolveWhatsAppSource(msg));
89
+ }
90
+ /**
91
+ * @mention gate for groups (mirrors `slack`/`line`'s `requireMention`,
92
+ * default true — only effective in groups, DMs always pass). Unlike Slack
93
+ * (which infers "was mentioned" from the event *type* being `app_mention`)
94
+ * Baileys hands every message's mentioned-JID list directly on
95
+ * `contextInfo.mentionedJid` — no separate text-scanning heuristic needed,
96
+ * just check one of the bot's own JIDs is in that list.
97
+ *
98
+ * Under WhatsApp's Linked ID (LID) privacy system a group can report the
99
+ * mentioned bot using its `@lid` identity even though `sock.user.id` (the
100
+ * phone-number JID we log in with) is the classic `@s.whatsapp.net` form —
101
+ * two different strings for the same bot. Pass `botLid` (`sock.user.lid`)
102
+ * alongside `botJid` (`sock.user.id`) so either form matches; omit it if
103
+ * unknown (older Baileys / non-LID accounts) and only `botJid` is checked.
104
+ */
105
+ function wasBotMentioned(mentionedJids, botJid, botLid) {
106
+ if (!botJid && !botLid)
107
+ return false;
108
+ const botIdentities = [botJid, botLid].filter((v) => !!v).map(normalizeWhatsAppJid);
109
+ return mentionedJids.some((jid) => botIdentities.includes(normalizeWhatsAppJid(jid)));
110
+ }
111
+ /**
112
+ * Canonical form of a WhatsApp JID for equality checks.
113
+ *
114
+ * WhatsApp JIDs occasionally carry a ":<device>" suffix on the *user* part
115
+ * (multi-device, e.g. "66811112222:5@s.whatsapp.net") — strip only that
116
+ * segment, not everything after the first colon, or the "@server" half gets
117
+ * dropped too and a same-user-different-device JID never matches at all
118
+ * (splitting on ':' before splitting on '@' would do exactly that). The
119
+ * `@lid` vs `@s.whatsapp.net` distinction is preserved: those are genuinely
120
+ * different addresses, and callers that must accept either (wasBotMentioned)
121
+ * normalize both forms and compare against the set.
122
+ *
123
+ * Shared by the INBOUND mention gate (wasBotMentioned above) and the OUTBOUND
124
+ * `@digits` → `mentions:[jid]` matcher in WhatsAppManager, so both sides agree
125
+ * on what "the same participant" means.
126
+ */
127
+ function normalizeWhatsAppJid(jid) {
128
+ const [userPart, server] = jid.split('@');
129
+ return server ? `${userPart.split(':')[0]}@${server}` : userPart.split(':')[0];
130
+ }
131
+ /** The user half of a normalized JID — "66811112222" from "66811112222:5@s.whatsapp.net". */
132
+ function whatsAppJidUser(jid) {
133
+ return normalizeWhatsAppJid(jid).split('@')[0] ?? '';
134
+ }
135
+ //# sourceMappingURL=whatsapp-access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whatsapp-access.js","sourceRoot":"","sources":["../../src/api/whatsapp-access.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * WhatsApp Business Cloud API DM access control — pure, stateless sender gate.
3
+ *
4
+ * Structural port of `whatsapp-access.ts` (the Baileys sibling), simplified:
5
+ * the Cloud API has no group concept (a Business phone number cannot be added
6
+ * to a group the way a personal/linked number can — every inbound message is
7
+ * a 1:1 conversation with the business), so there is no group tier here at
8
+ * all — `kind` is `'user' | 'other'`, not `'user' | 'group' | 'other'`.
9
+ *
10
+ * ⚠️ FOOTGUN: Cloud API's inbound `from` is a BARE PHONE-NUMBER STRING (e.g.
11
+ * `"66812345678"` — digits only, no `+`, no `@s.whatsapp.net` suffix), NOT a
12
+ * JID like Baileys hands `whatsapp-access.ts`. `dmAllowlist` entries here
13
+ * MUST be bare digits — pasting a JID (`"66812345678@s.whatsapp.net"`) or a
14
+ * `+`-prefixed E.164 string will silently never match. This is the exact
15
+ * same class of footgun Slack/LINE's own doc comments call out for their
16
+ * allowlists, just with a different wrong shape (JID/`+` instead of a name).
17
+ *
18
+ * Default (policy absent) is CLOSED: only ids in the allowlist pass. Set
19
+ * `dmPolicy: 'open'` to restore "reply to anyone" behavior.
20
+ */
21
+ export declare function isWhatsAppCloudSenderAllowed(policy: 'open' | 'allowlist' | 'disabled' | undefined, allowlist: string[] | undefined, id: string): boolean;
22
+ export type WhatsAppCloudSourceKind = 'user' | 'other';
23
+ export interface ResolvedWhatsAppCloudSource {
24
+ /** Conversation key — the reply target: the sender's bare phone-number string. */
25
+ conversationId: string;
26
+ /** The human who sent the message — always the same as conversationId (no groups). */
27
+ senderId: string;
28
+ kind: WhatsAppCloudSourceKind;
29
+ }
30
+ /**
31
+ * Map a raw inbound Cloud API `from` field to {conversationId, senderId, kind}.
32
+ * `from` is always a bare phone-number string on a real webhook payload —
33
+ * empty/missing is the only "other" case, since there is no group tier to
34
+ * misclassify into.
35
+ */
36
+ export declare function resolveWhatsAppCloudSource(from: string | undefined | null): ResolvedWhatsAppCloudSource;
37
+ /** The subset of `whatsapp_cloud` config the conversation gate reads. */
38
+ export interface WhatsAppCloudAccessConfig {
39
+ dmPolicy?: 'open' | 'allowlist' | 'disabled';
40
+ dmAllowlist?: string[];
41
+ }
42
+ /**
43
+ * Gate an already-resolved WhatsApp Cloud source. Every source is a DM
44
+ * (`kind === 'user'`), gated on the sender's bare phone number against
45
+ * `dmPolicy`/`dmAllowlist`. Unknown/empty source kinds are denied.
46
+ */
47
+ export declare function isResolvedWhatsAppCloudSourceAllowed(cfg: WhatsAppCloudAccessConfig | undefined, resolved: ResolvedWhatsAppCloudSource): boolean;
48
+ //# sourceMappingURL=whatsapp-cloud-access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whatsapp-cloud-access.d.ts","sourceRoot":"","sources":["../../src/api/whatsapp-cloud-access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,GAAG,SAAS,EACrD,SAAS,EAAE,MAAM,EAAE,GAAG,SAAS,EAC/B,EAAE,EAAE,MAAM,GACT,OAAO,CAKT;AAED,MAAM,MAAM,uBAAuB,GAAG,MAAM,GAAG,OAAO,CAAC;AAEvD,MAAM,WAAW,2BAA2B;IAC1C,kFAAkF;IAClF,cAAc,EAAE,MAAM,CAAC;IACvB,sFAAsF;IACtF,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,uBAAuB,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,2BAA2B,CAIvG;AAED,yEAAyE;AACzE,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAC;IAC7C,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,oCAAoC,CAClD,GAAG,EAAE,yBAAyB,GAAG,SAAS,EAC1C,QAAQ,EAAE,2BAA2B,GACpC,OAAO,CAMT"}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isWhatsAppCloudSenderAllowed = isWhatsAppCloudSenderAllowed;
4
+ exports.resolveWhatsAppCloudSource = resolveWhatsAppCloudSource;
5
+ exports.isResolvedWhatsAppCloudSourceAllowed = isResolvedWhatsAppCloudSourceAllowed;
6
+ /**
7
+ * WhatsApp Business Cloud API DM access control — pure, stateless sender gate.
8
+ *
9
+ * Structural port of `whatsapp-access.ts` (the Baileys sibling), simplified:
10
+ * the Cloud API has no group concept (a Business phone number cannot be added
11
+ * to a group the way a personal/linked number can — every inbound message is
12
+ * a 1:1 conversation with the business), so there is no group tier here at
13
+ * all — `kind` is `'user' | 'other'`, not `'user' | 'group' | 'other'`.
14
+ *
15
+ * ⚠️ FOOTGUN: Cloud API's inbound `from` is a BARE PHONE-NUMBER STRING (e.g.
16
+ * `"66812345678"` — digits only, no `+`, no `@s.whatsapp.net` suffix), NOT a
17
+ * JID like Baileys hands `whatsapp-access.ts`. `dmAllowlist` entries here
18
+ * MUST be bare digits — pasting a JID (`"66812345678@s.whatsapp.net"`) or a
19
+ * `+`-prefixed E.164 string will silently never match. This is the exact
20
+ * same class of footgun Slack/LINE's own doc comments call out for their
21
+ * allowlists, just with a different wrong shape (JID/`+` instead of a name).
22
+ *
23
+ * Default (policy absent) is CLOSED: only ids in the allowlist pass. Set
24
+ * `dmPolicy: 'open'` to restore "reply to anyone" behavior.
25
+ */
26
+ function isWhatsAppCloudSenderAllowed(policy, allowlist, id) {
27
+ if (policy === 'open')
28
+ return true;
29
+ if (policy === 'disabled')
30
+ return false;
31
+ // 'allowlist' OR undefined (closed default) → only listed ids pass.
32
+ return !!id && (allowlist ?? []).includes(id);
33
+ }
34
+ /**
35
+ * Map a raw inbound Cloud API `from` field to {conversationId, senderId, kind}.
36
+ * `from` is always a bare phone-number string on a real webhook payload —
37
+ * empty/missing is the only "other" case, since there is no group tier to
38
+ * misclassify into.
39
+ */
40
+ function resolveWhatsAppCloudSource(from) {
41
+ const id = from ?? '';
42
+ if (!id)
43
+ return { conversationId: '', senderId: '', kind: 'other' };
44
+ return { conversationId: id, senderId: id, kind: 'user' };
45
+ }
46
+ /**
47
+ * Gate an already-resolved WhatsApp Cloud source. Every source is a DM
48
+ * (`kind === 'user'`), gated on the sender's bare phone number against
49
+ * `dmPolicy`/`dmAllowlist`. Unknown/empty source kinds are denied.
50
+ */
51
+ function isResolvedWhatsAppCloudSourceAllowed(cfg, resolved) {
52
+ const { conversationId, senderId, kind } = resolved;
53
+ if (kind === 'user') {
54
+ return isWhatsAppCloudSenderAllowed(cfg?.dmPolicy, cfg?.dmAllowlist, senderId || conversationId);
55
+ }
56
+ return false;
57
+ }
58
+ //# sourceMappingURL=whatsapp-cloud-access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whatsapp-cloud-access.js","sourceRoot":"","sources":["../../src/api/whatsapp-cloud-access.ts"],"names":[],"mappings":";;AAoBA,oEASC;AAkBD,gEAIC;AAaD,oFASC;AAzED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAgB,4BAA4B,CAC1C,MAAqD,EACrD,SAA+B,EAC/B,EAAU;IAEV,IAAI,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,MAAM,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IACxC,oEAAoE;IACpE,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;AAChD,CAAC;AAYD;;;;;GAKG;AACH,SAAgB,0BAA0B,CAAC,IAA+B;IACxE,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC;IACtB,IAAI,CAAC,EAAE;QAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACpE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC5D,CAAC;AAQD;;;;GAIG;AACH,SAAgB,oCAAoC,CAClD,GAA0C,EAC1C,QAAqC;IAErC,MAAM,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC;IACpD,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,OAAO,4BAA4B,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,IAAI,cAAc,CAAC,CAAC;IACnG,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,143 @@
1
+ import { WHATSAPP_ACK_EMOJI } from '../shared/whatsapp-ack';
2
+ /**
3
+ * Cloud API hard limit for a text message body. Longer replies are split
4
+ * across several messages rather than being rejected by Meta (error 131009)
5
+ * or silently truncated.
6
+ */
7
+ export declare const WHATSAPP_CLOUD_MAX_TEXT_CHARS = 4096;
8
+ /**
9
+ * Meta's hard cap on reply buttons in an `interactive: {type: 'button'}`
10
+ * message. Not a soft/UX limit — the Graph API rejects a 4th button outright,
11
+ * so `sendInteractiveButtons` refuses locally with a readable message rather
12
+ * than letting the agent see an opaque Meta error code.
13
+ */
14
+ export declare const WHATSAPP_CLOUD_MAX_BUTTONS = 3;
15
+ /** A single reply button: `id` is what comes back on tap, `title` is what the user sees. */
16
+ export interface WhatsAppCloudButton {
17
+ id: string;
18
+ title: string;
19
+ }
20
+ /** One row of a list message. `description` is the optional grey sub-line. */
21
+ export interface WhatsAppCloudListRow {
22
+ id: string;
23
+ title: string;
24
+ description?: string;
25
+ }
26
+ /** A list message's section — a titled group of rows. */
27
+ export interface WhatsAppCloudListSection {
28
+ title: string;
29
+ rows: WhatsAppCloudListRow[];
30
+ }
31
+ export { WHATSAPP_ACK_EMOJI };
32
+ export interface WhatsAppCloudClientOptions {
33
+ accessToken: string;
34
+ phoneNumberId: string;
35
+ logDir: string;
36
+ /** Test-only override for the Graph API base URL. Production uses the real default. */
37
+ apiBase?: string;
38
+ }
39
+ export interface WhatsAppCloudApiResponse {
40
+ error?: {
41
+ message?: string;
42
+ type?: string;
43
+ code?: number;
44
+ [key: string]: unknown;
45
+ };
46
+ [key: string]: unknown;
47
+ }
48
+ export declare class WhatsAppCloudClient {
49
+ private readonly accessToken;
50
+ private readonly phoneNumberId;
51
+ private readonly apiBase;
52
+ private readonly logger;
53
+ constructor(opts: WhatsAppCloudClientOptions);
54
+ private call;
55
+ /**
56
+ * Verify the token + phone number id — used both as the connect flow's
57
+ * "Save"-time check (router.ts) and reused here for any other caller that
58
+ * wants a live credential check without duplicating the request shape.
59
+ */
60
+ verifyCredentials(): Promise<{
61
+ ok: boolean;
62
+ error?: string;
63
+ }>;
64
+ /**
65
+ * Send a plain text message, splitting anything over the Cloud API's
66
+ * 4096-char body limit into several messages (one Graph API call per chunk,
67
+ * sent in order).
68
+ *
69
+ * `quotedMessageId` makes the FIRST chunk a quote-reply to that inbound
70
+ * message (Meta's outbound `context: {message_id}`); follow-on chunks are
71
+ * plain so the conversation doesn't show the same quote block repeated.
72
+ *
73
+ * Returns the first errored response if any chunk failed (so existing
74
+ * `if (sent.error)` callers still see the failure), otherwise the last
75
+ * successful one.
76
+ */
77
+ sendText(to: string, body: string, quotedMessageId?: string): Promise<WhatsAppCloudApiResponse>;
78
+ /**
79
+ * Send up to 3 tappable reply buttons under a body of text (Phase 3).
80
+ *
81
+ * A tap comes back through the webhook as `type: 'interactive'` with a
82
+ * `button_reply` — the inbound normalizer turns that into plain text
83
+ * (the button's title), so the agent reads a tap exactly as if the user had
84
+ * typed the label. See normalizeWhatsAppCloudMessage's interactive branch.
85
+ *
86
+ * THROWS (rather than returning an `{error}` response) when more than
87
+ * WHATSAPP_CLOUD_MAX_BUTTONS are passed or the list is empty: both are
88
+ * caller bugs that Meta would reject anyway, and a thrown, readable message
89
+ * reaches the agent through the MCP tool's catch as actionable text.
90
+ */
91
+ sendInteractiveButtons(to: string, bodyText: string, buttons: WhatsAppCloudButton[]): Promise<WhatsAppCloudApiResponse>;
92
+ /**
93
+ * Send a list message — a single button that opens a picker of grouped rows
94
+ * (Phase 3). The way past the 3-button ceiling above.
95
+ *
96
+ * A pick comes back as `type: 'interactive'` with a `list_reply`, handled by
97
+ * the same normalizer branch as button taps.
98
+ */
99
+ sendInteractiveList(to: string, bodyText: string, buttonLabel: string, sections: WhatsAppCloudListSection[]): Promise<WhatsAppCloudApiResponse>;
100
+ /**
101
+ * Send a pre-approved message template (Phase 3) — the ONLY way to open a
102
+ * conversation outside WhatsApp's 24h customer-service window, which is why
103
+ * the caller must be gated on `whatsapp_cloud.templatesEnabled` (see
104
+ * mcp/tools/whatsapp-cloud/module.ts; this method itself does not gate).
105
+ *
106
+ * `components` is Meta's variable-substitution array. It is passed through
107
+ * VERBATIM and deliberately NOT validated or typed beyond `unknown[]`: its
108
+ * required shape depends on the individual template's definition in Meta's
109
+ * Business Manager, which this gateway has no visibility into.
110
+ */
111
+ sendTemplate(to: string, templateName: string, languageCode: string, components?: unknown[]): Promise<WhatsAppCloudApiResponse>;
112
+ /**
113
+ * Mark an inbound message as read (the blue double-tick on the sender's
114
+ * side). Best-effort: logged, never thrown — a read receipt is UX polish,
115
+ * not correctness-critical, exactly like Slack's ack reaction.
116
+ */
117
+ markAsRead(messageId: string): Promise<void>;
118
+ /**
119
+ * Ack-reaction (ported from `slack-client.ts`'s addReaction): added to the
120
+ * inbound message on receipt, cleared once the agent's reply lands. An
121
+ * EMPTY `emoji` is how the Cloud API removes an existing reaction — see
122
+ * removeReaction below. Best-effort, same as markAsRead.
123
+ */
124
+ sendReaction(to: string, messageId: string, emoji?: string): Promise<void>;
125
+ /** Clear a reaction — the same endpoint with an empty-string emoji. */
126
+ removeReaction(to: string, messageId: string): Promise<void>;
127
+ /**
128
+ * Upload a local file to the Cloud API's media store, returning its
129
+ * `media_id` — a prerequisite step before `sendImage`/`sendDocument`
130
+ * (the Cloud API sends media by id, not by raw bytes or URL, for
131
+ * gateway-originated files).
132
+ */
133
+ uploadMedia(filePath: string, mimeType: string): Promise<{
134
+ mediaId: string;
135
+ } | {
136
+ error: string;
137
+ }>;
138
+ /** Send an already-uploaded image by media id, with an optional caption. */
139
+ sendImage(to: string, mediaId: string, caption?: string): Promise<WhatsAppCloudApiResponse>;
140
+ /** Send an already-uploaded document by media id, with a filename and optional caption. */
141
+ sendDocument(to: string, mediaId: string, filename: string, caption?: string): Promise<WhatsAppCloudApiResponse>;
142
+ }
143
+ //# sourceMappingURL=whatsapp-cloud-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whatsapp-cloud-client.d.ts","sourceRoot":"","sources":["../../src/api/whatsapp-cloud-client.ts"],"names":[],"mappings":"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"}