@0xmaxma/claude-gateway 1.8.9 → 1.8.11

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 (121) hide show
  1. package/README.md +46 -6
  2. package/config.template.json +2 -1
  3. package/dist/agent/runner.d.ts +86 -1
  4. package/dist/agent/runner.d.ts.map +1 -1
  5. package/dist/agent/runner.js +396 -9
  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 +1028 -6
  9. package/dist/api/router.js.map +1 -1
  10. package/dist/api/share-router.d.ts.map +1 -1
  11. package/dist/api/share-router.js +24 -0
  12. package/dist/api/share-router.js.map +1 -1
  13. package/dist/api/webhooks-router.d.ts +2 -0
  14. package/dist/api/webhooks-router.d.ts.map +1 -1
  15. package/dist/api/webhooks-router.js +2 -0
  16. package/dist/api/webhooks-router.js.map +1 -1
  17. package/dist/api/whatsapp-access.d.ts +123 -0
  18. package/dist/api/whatsapp-access.d.ts.map +1 -0
  19. package/dist/api/whatsapp-access.js +135 -0
  20. package/dist/api/whatsapp-access.js.map +1 -0
  21. package/dist/api/whatsapp-cloud-access.d.ts +48 -0
  22. package/dist/api/whatsapp-cloud-access.d.ts.map +1 -0
  23. package/dist/api/whatsapp-cloud-access.js +58 -0
  24. package/dist/api/whatsapp-cloud-access.js.map +1 -0
  25. package/dist/api/whatsapp-cloud-client.d.ts +143 -0
  26. package/dist/api/whatsapp-cloud-client.d.ts.map +1 -0
  27. package/dist/api/whatsapp-cloud-client.js +298 -0
  28. package/dist/api/whatsapp-cloud-client.js.map +1 -0
  29. package/dist/api/whatsapp-cloud-webhook-router.d.ts +132 -0
  30. package/dist/api/whatsapp-cloud-webhook-router.d.ts.map +1 -0
  31. package/dist/api/whatsapp-cloud-webhook-router.js +494 -0
  32. package/dist/api/whatsapp-cloud-webhook-router.js.map +1 -0
  33. package/dist/cli/args.d.ts +34 -0
  34. package/dist/cli/args.d.ts.map +1 -1
  35. package/dist/cli/args.js +55 -0
  36. package/dist/cli/args.js.map +1 -1
  37. package/dist/cli/commands/app.d.ts +28 -0
  38. package/dist/cli/commands/app.d.ts.map +1 -0
  39. package/dist/cli/commands/app.js +411 -0
  40. package/dist/cli/commands/app.js.map +1 -0
  41. package/dist/cli/commands/doctor.d.ts.map +1 -1
  42. package/dist/cli/commands/doctor.js +77 -16
  43. package/dist/cli/commands/doctor.js.map +1 -1
  44. package/dist/cli/commands/service.d.ts.map +1 -1
  45. package/dist/cli/commands/service.js +373 -56
  46. package/dist/cli/commands/service.js.map +1 -1
  47. package/dist/cli/commands/update.d.ts.map +1 -1
  48. package/dist/cli/commands/update.js +1 -17
  49. package/dist/cli/commands/update.js.map +1 -1
  50. package/dist/cli/http-client.d.ts +8 -0
  51. package/dist/cli/http-client.d.ts.map +1 -1
  52. package/dist/cli/http-client.js +5 -2
  53. package/dist/cli/http-client.js.map +1 -1
  54. package/dist/cli/index.d.ts.map +1 -1
  55. package/dist/cli/index.js +16 -4
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/cli/prompt.d.ts +17 -2
  58. package/dist/cli/prompt.d.ts.map +1 -1
  59. package/dist/cli/prompt.js +33 -2
  60. package/dist/cli/prompt.js.map +1 -1
  61. package/dist/config/loader.d.ts +8 -0
  62. package/dist/config/loader.d.ts.map +1 -1
  63. package/dist/config/loader.js +22 -4
  64. package/dist/config/loader.js.map +1 -1
  65. package/dist/config/watcher.d.ts.map +1 -1
  66. package/dist/config/watcher.js +12 -0
  67. package/dist/config/watcher.js.map +1 -1
  68. package/dist/config/whatsapp-accounts.d.ts +77 -0
  69. package/dist/config/whatsapp-accounts.d.ts.map +1 -0
  70. package/dist/config/whatsapp-accounts.js +218 -0
  71. package/dist/config/whatsapp-accounts.js.map +1 -0
  72. package/dist/history/db.d.ts.map +1 -1
  73. package/dist/history/db.js +43 -11
  74. package/dist/history/db.js.map +1 -1
  75. package/dist/history/types.d.ts +18 -1
  76. package/dist/history/types.d.ts.map +1 -1
  77. package/dist/history/types.js +1 -1
  78. package/dist/history/types.js.map +1 -1
  79. package/dist/index.js +26 -0
  80. package/dist/index.js.map +1 -1
  81. package/dist/session/process.d.ts.map +1 -1
  82. package/dist/session/process.js +37 -0
  83. package/dist/session/process.js.map +1 -1
  84. package/dist/session/store.d.ts +3 -2
  85. package/dist/session/store.d.ts.map +1 -1
  86. package/dist/session/store.js +1 -1
  87. package/dist/session/store.js.map +1 -1
  88. package/dist/share/session-video-catalog.d.ts +19 -0
  89. package/dist/share/session-video-catalog.d.ts.map +1 -0
  90. package/dist/share/session-video-catalog.js +119 -0
  91. package/dist/share/session-video-catalog.js.map +1 -0
  92. package/dist/shared/image-optimize.d.ts +32 -0
  93. package/dist/shared/image-optimize.d.ts.map +1 -0
  94. package/dist/shared/image-optimize.js +197 -0
  95. package/dist/shared/image-optimize.js.map +1 -0
  96. package/dist/shared/image-sniff.d.ts +9 -0
  97. package/dist/shared/image-sniff.d.ts.map +1 -1
  98. package/dist/shared/image-sniff.js +13 -0
  99. package/dist/shared/image-sniff.js.map +1 -1
  100. package/dist/shared/text-chunk.d.ts +37 -0
  101. package/dist/shared/text-chunk.d.ts.map +1 -0
  102. package/dist/shared/text-chunk.js +105 -0
  103. package/dist/shared/text-chunk.js.map +1 -0
  104. package/dist/shared/whatsapp-ack.d.ts +13 -0
  105. package/dist/shared/whatsapp-ack.d.ts.map +1 -0
  106. package/dist/shared/whatsapp-ack.js +16 -0
  107. package/dist/shared/whatsapp-ack.js.map +1 -0
  108. package/dist/types.d.ts +183 -0
  109. package/dist/types.d.ts.map +1 -1
  110. package/dist/whatsapp/manager.d.ts +173 -0
  111. package/dist/whatsapp/manager.d.ts.map +1 -0
  112. package/dist/whatsapp/manager.js +838 -0
  113. package/dist/whatsapp/manager.js.map +1 -0
  114. package/mcp/server.ts +4 -0
  115. package/mcp/tools/image/module.ts +6 -1
  116. package/mcp/tools/telegram/receiver-server.ts +15 -8
  117. package/mcp/tools/telegram/reply-attachment.ts +81 -0
  118. package/mcp/tools/video/module.ts +6 -1
  119. package/mcp/tools/whatsapp/module.ts +162 -0
  120. package/mcp/tools/whatsapp-cloud/module.ts +536 -0
  121. package/package.json +7 -2
@@ -0,0 +1,494 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.extractInboundMessages = extractInboundMessages;
37
+ exports.synthesizeVCard = synthesizeVCard;
38
+ exports.normalizeWhatsAppCloudMessage = normalizeWhatsAppCloudMessage;
39
+ exports.verifyMetaSignature = verifyMetaSignature;
40
+ exports.createWhatsAppCloudWebhookHandler = createWhatsAppCloudWebhookHandler;
41
+ const crypto_1 = require("crypto");
42
+ const fs = __importStar(require("fs"));
43
+ const os = __importStar(require("os"));
44
+ const path = __importStar(require("path"));
45
+ const logger_1 = require("../logger");
46
+ const whatsapp_cloud_access_1 = require("./whatsapp-cloud-access");
47
+ const pending_senders_1 = require("./pending-senders");
48
+ const whatsapp_cloud_client_1 = require("./whatsapp-cloud-client");
49
+ const media_store_1 = require("../history/media-store");
50
+ const image_sniff_1 = require("../shared/image-sniff");
51
+ const WHATSAPP_CLOUD_API_BASE = 'https://graph.facebook.com/v20.0';
52
+ // Inbound media cap. Sourced from MediaStore (where the downloaded bytes end
53
+ // up) for both images and documents — a router-local literal could drift
54
+ // from what the store then rejects.
55
+ const MAX_MEDIA_BYTES = media_store_1.MediaStore.maxUploadBytes;
56
+ // Meta retries a webhook delivery if our ack is slow/dropped — message.id is
57
+ // the dedup key. A bounded, age-pruned map is enough (mirrors Slack's
58
+ // seenEventIds): it only needs to outlive Meta's short retry window, not forever.
59
+ const WHATSAPP_CLOUD_MESSAGE_ID_TTL_MS = 10 * 60000;
60
+ // The returned media download `url` must resolve to a Meta-owned host before
61
+ // the bearer token is ever attached to the second GET — see
62
+ // downloadWhatsAppCloudMedia's doc comment (mirrors downloadSlackImage's
63
+ // host-check-before-sending-token defense).
64
+ const MEDIA_HOST_ALLOWLIST = [/\.fbcdn\.net$/, /^graph\.facebook\.com$/, /^lookaside\.fbsbx\.com$/];
65
+ /**
66
+ * Extract the inbound message list from a webhook `value` object — returns
67
+ * [] for a status-only batch (our own outbound delivery receipts) or any
68
+ * payload shape without a `messages` array at all, so callers never try to
69
+ * normalize a status update as an inbound message.
70
+ */
71
+ function extractInboundMessages(value) {
72
+ return value?.messages ?? [];
73
+ }
74
+ /**
75
+ * Build a minimal vCard from Meta's structured contact payload.
76
+ *
77
+ * The Cloud API does NOT hand over a vCard string (Baileys does — see
78
+ * manager.ts's contactMessage branch, which uses `contactMessage.vcard`
79
+ * verbatim), it hands over decomposed JSON. Synthesizing a 3.0 vCard here
80
+ * means BOTH WhatsApp channels put the same kind of value in the same
81
+ * `meta.vcard` key, so the agent never needs channel-specific handling.
82
+ * Returns undefined when there is neither a name nor a phone to put in it.
83
+ */
84
+ function synthesizeVCard(contact) {
85
+ const name = contact?.name?.formatted_name?.trim();
86
+ const phone = contact?.phones?.find((p) => p.phone)?.phone?.trim();
87
+ if (!name && !phone)
88
+ return undefined;
89
+ const lines = ['BEGIN:VCARD', 'VERSION:3.0'];
90
+ if (name)
91
+ lines.push(`FN:${name}`);
92
+ if (phone)
93
+ lines.push(`TEL:${phone}`);
94
+ lines.push('END:VCARD');
95
+ return lines.join('\n');
96
+ }
97
+ /**
98
+ * Normalize an inbound Cloud API message into the gateway's {content, meta}
99
+ * intake shape. Returns null when `from` is missing/empty (no reply target).
100
+ * `content` is the text body for `type: 'text'`, the caption (possibly
101
+ * empty) for `type: 'image'`/`'document'`, or a short human summary for
102
+ * `type: 'location'`, or the selected option's title for `type: 'interactive'`
103
+ * (a button tap / list pick) — remaining types (audio, video, ...) are still
104
+ * out of scope and forward with empty content rather than being dropped, same
105
+ * posture as Slack's "no text → empty content, not null".
106
+ *
107
+ * Stays synchronous and pure: an attached image/document/sticker's bytes are
108
+ * fetched by the async handler AFTER this returns, which sets
109
+ * `meta.image_path`/`meta.document_path`/`meta.sticker_path` on the object
110
+ * built here.
111
+ */
112
+ function normalizeWhatsAppCloudMessage(msg, resolved) {
113
+ const r = resolved ?? (0, whatsapp_cloud_access_1.resolveWhatsAppCloudSource)(msg.from);
114
+ if (r.kind === 'other' || !r.conversationId)
115
+ return null;
116
+ const meta = {
117
+ source: 'whatsapp_cloud',
118
+ chat_id: r.conversationId,
119
+ user_id: r.senderId,
120
+ user: r.senderId,
121
+ message_id: msg.id ?? '',
122
+ };
123
+ // Reply context. Meta's webhook carries ONLY the quoted message's id — the
124
+ // quoted text and its sender are NOT inlined (a real Cloud API limitation,
125
+ // not a gap here; retrieving them would need our own store of previously
126
+ // seen messages). So `replied_user`/`replied_text` are deliberately left
127
+ // unset for this channel, and buildChannelXml emits `<replied
128
+ // message_id=… user="">` with an empty body. Baileys, by contrast, does
129
+ // inline the quote and populates all three.
130
+ if (msg.context?.id)
131
+ meta.replied_message_id = msg.context.id;
132
+ let content = '';
133
+ if (msg.type === 'text')
134
+ content = msg.text?.body ?? '';
135
+ else if (msg.type === 'image')
136
+ content = msg.image?.caption ?? '';
137
+ else if (msg.type === 'document')
138
+ content = msg.document?.caption ?? '';
139
+ else if (msg.type === 'location' && msg.location) {
140
+ const { latitude, longitude, name, address } = msg.location;
141
+ if (typeof latitude === 'number')
142
+ meta.location_lat = String(latitude);
143
+ if (typeof longitude === 'number')
144
+ meta.location_lng = String(longitude);
145
+ // A pinned place carries a name/address; a raw dropped pin carries only
146
+ // coordinates, which already ride along in meta — hence the generic
147
+ // fallback rather than repeating the numbers in the text.
148
+ content = [name, address].filter(Boolean).join(', ') || '[Location shared]';
149
+ }
150
+ else if (msg.type === 'contacts') {
151
+ const vcard = synthesizeVCard(msg.contacts?.[0]);
152
+ if (vcard)
153
+ meta.vcard = vcard;
154
+ }
155
+ else if (msg.type === 'interactive') {
156
+ // A button tap / list pick (Phase 3). `content` becomes the DISPLAY TITLE
157
+ // of whatever the user selected, so the turn reads to the agent exactly as
158
+ // if they had typed that label — no interactive-aware agent-side handling
159
+ // is needed for the common case. The machine-readable `id` rides along in
160
+ // meta.interactive_id for agents whose logic branches on the id instead
161
+ // (ids are stable; titles are what the user reads and may be localized).
162
+ const selected = msg.interactive?.button_reply ?? msg.interactive?.list_reply;
163
+ if (selected) {
164
+ content = selected.title ?? '';
165
+ if (selected.id)
166
+ meta.interactive_id = selected.id;
167
+ }
168
+ }
169
+ return { content, meta };
170
+ }
171
+ /** Verify `X-Hub-Signature-256` (HMAC-SHA256 of the raw body — no timestamp component). */
172
+ function verifyMetaSignature(rawBody, appSecret, sigHeader) {
173
+ if (!appSecret || !sigHeader)
174
+ return false;
175
+ const prefix = 'sha256=';
176
+ if (!sigHeader.startsWith(prefix))
177
+ return false;
178
+ const expected = `${prefix}${(0, crypto_1.createHmac)('sha256', appSecret).update(rawBody).digest('hex')}`;
179
+ const expectedBuf = Buffer.from(expected, 'utf8');
180
+ const actualBuf = Buffer.from(sigHeader, 'utf8');
181
+ if (expectedBuf.length !== actualBuf.length)
182
+ return false;
183
+ return (0, crypto_1.timingSafeEqual)(expectedBuf, actualBuf);
184
+ }
185
+ /** Constant-time string equality — same posture as verifyMetaSignature above. */
186
+ function safeStringEqual(a, b) {
187
+ const aBuf = Buffer.from(a, 'utf8');
188
+ const bBuf = Buffer.from(b, 'utf8');
189
+ if (aBuf.length !== bBuf.length)
190
+ return false;
191
+ return (0, crypto_1.timingSafeEqual)(aBuf, bBuf);
192
+ }
193
+ /** Find the agent that has WhatsApp Cloud configured (mirrors resolveSlackAgent). */
194
+ function resolveWhatsAppCloudAgent(agents, agentId) {
195
+ if (agentId) {
196
+ const r = agents.get(agentId);
197
+ return r && r.getAgentConfig().whatsapp_cloud?.appSecret ? r : null;
198
+ }
199
+ for (const runner of agents.values()) {
200
+ if (runner.getAgentConfig().whatsapp_cloud?.appSecret)
201
+ return runner;
202
+ }
203
+ return null;
204
+ }
205
+ /**
206
+ * One-time pairing-code message — same visual-match-code contract as
207
+ * Slack/LINE's pairingMessage. DM-only (no "in this channel/group" phrasing
208
+ * needed — there is no group tier on this channel).
209
+ */
210
+ function pairingMessage(code) {
211
+ return (`รหัสจับคู่ (pairing code) ของคุณคือ: ${code}\n` +
212
+ `กรุณาแจ้งรหัสนี้ให้แอดมินเพื่อขอเปิดใช้งานบอท (ไม่ต้องพิมพ์รหัสตอบกลับ)\n\n` +
213
+ `Your pairing code: ${code}\n` +
214
+ `Share this code with the admin to get access. (No need to reply with it.)`);
215
+ }
216
+ /**
217
+ * Fetch an inbound media attachment's bytes and return them + the reported
218
+ * mime type. Two-step, per the Cloud API's media contract:
219
+ * 1. `GET /{media-id}?access_token=...` → `{url, mime_type}` — the
220
+ * returned `url` is short-lived and NOT itself bearer-protected the way
221
+ * `url_private` is on Slack, but IS host-restricted to Meta's own CDN.
222
+ * 2. `GET <url>` with the SAME bearer token.
223
+ *
224
+ * Step 2's host is validated against MEDIA_HOST_ALLOWLIST BEFORE the token is
225
+ * attached — defense in depth against a future payload-shape change or bug
226
+ * upstream of this call handing us an attacker-chosen `url` (mirrors
227
+ * downloadSlackImage's identical host-check-before-sending-token structure).
228
+ * Enforces MAX_MEDIA_BYTES against both the declared content-length and the
229
+ * actual streamed byte count. Returns null when the metadata step yields no
230
+ * `url`; throws on HTTP failure, an untrusted host, or an over-cap size — the
231
+ * caller logs and forwards the turn regardless.
232
+ */
233
+ async function downloadWhatsAppCloudMedia(accessToken, mediaId, apiBase) {
234
+ const metaRes = await fetch(`${apiBase}/${mediaId}?access_token=${encodeURIComponent(accessToken)}`);
235
+ if (!metaRes.ok)
236
+ throw new Error(`HTTP ${metaRes.status} fetching media metadata`);
237
+ const metaJson = (await metaRes.json());
238
+ if (!metaJson.url)
239
+ return null;
240
+ let host;
241
+ try {
242
+ host = new URL(metaJson.url).hostname;
243
+ }
244
+ catch {
245
+ throw new Error('invalid media url');
246
+ }
247
+ if (!MEDIA_HOST_ALLOWLIST.some((re) => re.test(host))) {
248
+ throw new Error(`refusing to send bearer token to untrusted media host: ${host}`);
249
+ }
250
+ const res = await fetch(metaJson.url, { headers: { Authorization: `Bearer ${accessToken}` } });
251
+ if (!res.ok)
252
+ throw new Error(`HTTP ${res.status} downloading media`);
253
+ const declared = Number(res.headers.get('content-length') ?? '');
254
+ if (Number.isFinite(declared) && declared > MAX_MEDIA_BYTES) {
255
+ throw new Error(`media exceeds ${MAX_MEDIA_BYTES} byte cap`);
256
+ }
257
+ let buf;
258
+ if (res.body) {
259
+ const chunks = [];
260
+ let total = 0;
261
+ for await (const chunk of res.body) {
262
+ total += chunk.byteLength;
263
+ if (total > MAX_MEDIA_BYTES)
264
+ throw new Error(`media exceeds ${MAX_MEDIA_BYTES} byte cap`);
265
+ chunks.push(Buffer.from(chunk));
266
+ }
267
+ buf = Buffer.concat(chunks);
268
+ }
269
+ else {
270
+ buf = Buffer.from(await res.arrayBuffer());
271
+ if (buf.length > MAX_MEDIA_BYTES)
272
+ throw new Error(`media exceeds ${MAX_MEDIA_BYTES} byte cap`);
273
+ }
274
+ return { buf, mimeType: metaJson.mime_type ?? 'application/octet-stream' };
275
+ }
276
+ function writeTempImage(buf, messageId) {
277
+ // messageId is Meta-webhook-supplied and not guaranteed to match wamid.*'s
278
+ // format — sanitize before it reaches a filename (see sanitizeFilenameId).
279
+ const suffix = `${(0, image_sniff_1.sanitizeFilenameId)(messageId)}-${Date.now()}`;
280
+ const dest = path.join(os.tmpdir(), `whatsapp-cloud-img-${suffix}.${(0, image_sniff_1.sniffImageExt)(buf)}`);
281
+ fs.writeFileSync(dest, buf, { mode: 0o600 });
282
+ return dest;
283
+ }
284
+ function writeTempDocument(buf, mimeType, filename, messageId) {
285
+ const suffix = `${(0, image_sniff_1.sanitizeFilenameId)(messageId)}-${Date.now()}`;
286
+ const extFromName = filename ? path.extname(filename) : '';
287
+ const ext = extFromName || (mimeType === 'application/pdf' ? '.pdf' : '');
288
+ const dest = path.join(os.tmpdir(), `whatsapp-cloud-doc-${suffix}${ext}`);
289
+ fs.writeFileSync(dest, buf, { mode: 0o600 });
290
+ return dest;
291
+ }
292
+ function createWhatsAppCloudWebhookHandler(agents, logDir, opts = {}) {
293
+ const logger = (0, logger_1.createLogger)('whatsapp-cloud-webhook', logDir);
294
+ const apiBase = opts.apiBase ?? WHATSAPP_CLOUD_API_BASE;
295
+ const seenMessageIds = new Map();
296
+ const isDuplicateMessage = (id) => {
297
+ const now = Date.now();
298
+ for (const [mid, ts] of seenMessageIds) {
299
+ if (now - ts > WHATSAPP_CLOUD_MESSAGE_ID_TTL_MS)
300
+ seenMessageIds.delete(mid);
301
+ }
302
+ if (seenMessageIds.has(id))
303
+ return true;
304
+ seenMessageIds.set(id, now);
305
+ return false;
306
+ };
307
+ // GET handshake (opposite of Slack's signed-POST url_verification): Meta
308
+ // calls this once when the webhook subscription is configured, comparing
309
+ // our echoed `hub.challenge` against what it sent. Must respond with the
310
+ // RAW challenge string, not JSON-wrapped, on match.
311
+ const verify = (req, res) => {
312
+ const agentId = req.params.agentId;
313
+ const runner = resolveWhatsAppCloudAgent(agents, agentId);
314
+ const mode = req.query['hub.mode'];
315
+ const token = req.query['hub.verify_token'];
316
+ const challenge = req.query['hub.challenge'];
317
+ const expected = runner?.getAgentConfig().whatsapp_cloud?.verifyToken;
318
+ if (mode === 'subscribe' && expected && typeof token === 'string' && safeStringEqual(token, expected)) {
319
+ res.status(200).type('text/plain').send(String(challenge ?? ''));
320
+ return;
321
+ }
322
+ logger.warn('WhatsApp Cloud webhook: verify handshake failed', { agentId, mode });
323
+ res.status(403).json({ error: 'verification failed' });
324
+ };
325
+ const handlePost = async (req, res) => {
326
+ const agentId = req.params.agentId;
327
+ const runner = resolveWhatsAppCloudAgent(agents, agentId);
328
+ if (!runner) {
329
+ res.status(404).json({ error: 'no WhatsApp Cloud-enabled agent' });
330
+ return;
331
+ }
332
+ const cfg = runner.getAgentConfig().whatsapp_cloud;
333
+ const secret = cfg?.appSecret ?? '';
334
+ const buf = Buffer.isBuffer(req.body) ? req.body : Buffer.from('');
335
+ if (!verifyMetaSignature(buf, secret, req.header('x-hub-signature-256'))) {
336
+ logger.warn('WhatsApp Cloud webhook rejected: bad signature', { agentId: runner.getAgentConfig().id });
337
+ res.status(401).json({ error: 'invalid signature' });
338
+ return;
339
+ }
340
+ let payload;
341
+ try {
342
+ payload = JSON.parse(buf.toString('utf8'));
343
+ }
344
+ catch (err) {
345
+ logger.warn('WhatsApp Cloud webhook: bad JSON', { error: err.message });
346
+ res.status(400).json({ error: 'bad JSON' });
347
+ return;
348
+ }
349
+ // Ack immediately (Meta retries if our ack is slow/dropped) — process after.
350
+ res.status(200).json({ ok: true });
351
+ const deniedAgentId = runner.getAgentConfig().id;
352
+ for (const entry of payload.entry ?? []) {
353
+ for (const change of entry.changes ?? []) {
354
+ const value = change.value;
355
+ const messages = extractInboundMessages(value);
356
+ if (messages.length === 0)
357
+ continue; // status-only batch, or no messages field at all
358
+ // contacts[] carries profile.name synchronously, keyed by wa_id — used
359
+ // for the pending-knock display name with no extra API call needed
360
+ // (unlike Slack's getUserDisplayName round-trip).
361
+ const nameByWaId = new Map();
362
+ for (const c of value?.contacts ?? []) {
363
+ if (c.wa_id && c.profile?.name)
364
+ nameByWaId.set(c.wa_id, c.profile.name);
365
+ }
366
+ for (const msg of messages) {
367
+ if (msg.id && isDuplicateMessage(msg.id)) {
368
+ logger.debug('WhatsApp Cloud webhook: duplicate message id (Meta retry), skipping', { id: msg.id });
369
+ continue;
370
+ }
371
+ const resolved = (0, whatsapp_cloud_access_1.resolveWhatsAppCloudSource)(msg.from);
372
+ if (resolved.kind === 'other' || !resolved.conversationId)
373
+ continue;
374
+ if (!(0, whatsapp_cloud_access_1.isResolvedWhatsAppCloudSourceAllowed)(cfg, resolved)) {
375
+ logger.debug('WhatsApp Cloud webhook: source not allowed', {
376
+ agentId: deniedAgentId,
377
+ policy: cfg?.dmPolicy ?? '(closed)',
378
+ conversationId: resolved.conversationId,
379
+ });
380
+ const knockId = resolved.senderId;
381
+ if (knockId) {
382
+ const isPairing = cfg?.pairing !== false && cfg?.dmPolicy !== 'open' && cfg?.dmPolicy !== 'disabled';
383
+ const prev = (0, pending_senders_1.getPendingSender)('whatsapp_cloud', deniedAgentId, knockId);
384
+ const code = prev?.code ?? (isPairing ? (0, pending_senders_1.generatePairingCode)() : undefined);
385
+ const displayName = nameByWaId.get(knockId);
386
+ const wasNew = (0, pending_senders_1.recordDeniedSender)('whatsapp_cloud', deniedAgentId, knockId, displayName, Date.now(), code);
387
+ // Send the pairing code exactly once — on first contact only.
388
+ if (isPairing && wasNew && code && cfg?.accessToken && cfg?.phoneNumberId) {
389
+ const client = new whatsapp_cloud_client_1.WhatsAppCloudClient({
390
+ accessToken: cfg.accessToken,
391
+ phoneNumberId: cfg.phoneNumberId,
392
+ logDir,
393
+ apiBase: opts.apiBase,
394
+ });
395
+ void client
396
+ .sendText(knockId, pairingMessage(code))
397
+ .catch((err) => logger.debug('WhatsApp Cloud pairing code reply failed', { error: err.message }));
398
+ }
399
+ }
400
+ continue;
401
+ }
402
+ const norm = normalizeWhatsAppCloudMessage(msg, resolved);
403
+ if (!norm)
404
+ continue;
405
+ // Receipt signals (Phase 2), both strictly best-effort and both
406
+ // gated on config — mirrors slack-webhook-router.ts's
407
+ // `void client.addReaction(...).catch(() => {})` call site: fired
408
+ // right after the access gate, never awaited, never able to fail
409
+ // the turn. The ack reaction is cleared by the
410
+ // `whatsapp_cloud_reply` MCP tool once the agent's reply lands.
411
+ if (msg.id && cfg?.accessToken && cfg?.phoneNumberId) {
412
+ const receiptClient = new whatsapp_cloud_client_1.WhatsAppCloudClient({
413
+ accessToken: cfg.accessToken,
414
+ phoneNumberId: cfg.phoneNumberId,
415
+ logDir,
416
+ apiBase: opts.apiBase,
417
+ });
418
+ if (cfg.sendReadReceipts !== false) {
419
+ void receiptClient.markAsRead(msg.id).catch(() => { });
420
+ }
421
+ if ((cfg.reactionLevel ?? 'ack') === 'ack') {
422
+ void receiptClient.sendReaction(resolved.conversationId, msg.id).catch(() => { });
423
+ }
424
+ }
425
+ // Inbound media (image AND document, both eager at receipt time):
426
+ // fetch the bytes with the access token and hand the agent an
427
+ // absolute path. Images use the existing meta.image_path contract;
428
+ // documents use the new meta.document_path key, but ONLY when the
429
+ // mime type passes MediaStore.isAllowedMime() (PDF only in
430
+ // practice today) — an unsupported document type is logged and
431
+ // skipped, but the turn still forwards with whatever caption it has.
432
+ //
433
+ // Stickers (Phase 2) ride the SAME download path — they are
434
+ // `image/webp` media, which MediaStore.isAllowedMime already
435
+ // permits (`image/` prefix) and sniffImageExt already recognizes
436
+ // (RIFF/WEBP magic bytes) — but land on their own
437
+ // meta.sticker_path key, never image_path, so the agent can tell a
438
+ // sticker apart from a photo.
439
+ if (cfg?.accessToken && (msg.type === 'image' || msg.type === 'document' || msg.type === 'sticker')) {
440
+ const mediaObj = msg.type === 'image' ? msg.image : msg.type === 'sticker' ? msg.sticker : msg.document;
441
+ if (mediaObj?.id) {
442
+ try {
443
+ const downloaded = await downloadWhatsAppCloudMedia(cfg.accessToken, mediaObj.id, apiBase);
444
+ if (downloaded) {
445
+ if (msg.type === 'image') {
446
+ norm.meta.image_path = writeTempImage(downloaded.buf, msg.id);
447
+ }
448
+ else if (msg.type === 'sticker') {
449
+ if (media_store_1.MediaStore.isAllowedMime(downloaded.mimeType)) {
450
+ norm.meta.sticker_path = writeTempImage(downloaded.buf, msg.id);
451
+ }
452
+ else {
453
+ logger.warn('WhatsApp Cloud webhook: sticker mime type not allowed, skipping download', {
454
+ mimeType: downloaded.mimeType,
455
+ });
456
+ }
457
+ }
458
+ else if (media_store_1.MediaStore.isAllowedMime(downloaded.mimeType)) {
459
+ norm.meta.document_path = writeTempDocument(downloaded.buf, downloaded.mimeType, mediaObj.filename, msg.id);
460
+ }
461
+ else {
462
+ logger.warn('WhatsApp Cloud webhook: document mime type not allowed, skipping download', {
463
+ mimeType: downloaded.mimeType,
464
+ });
465
+ }
466
+ }
467
+ }
468
+ catch (err) {
469
+ logger.warn('WhatsApp Cloud webhook: media download failed', {
470
+ mediaId: mediaObj.id,
471
+ error: err.message,
472
+ });
473
+ }
474
+ }
475
+ }
476
+ try {
477
+ await fetch(`http://127.0.0.1:${runner.getCallbackPort()}/channel`, {
478
+ method: 'POST',
479
+ headers: { 'Content-Type': 'application/json' },
480
+ body: JSON.stringify(norm),
481
+ });
482
+ }
483
+ catch (err) {
484
+ logger.error('WhatsApp Cloud webhook: failed to forward to callback', {
485
+ error: err.message,
486
+ });
487
+ }
488
+ }
489
+ }
490
+ }
491
+ };
492
+ return { verify, handlePost };
493
+ }
494
+ //# sourceMappingURL=whatsapp-cloud-webhook-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whatsapp-cloud-webhook-router.js","sourceRoot":"","sources":["../../src/api/whatsapp-cloud-webhook-router.ts"],"names":[],"mappings":"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"}
@@ -13,6 +13,40 @@ export interface ParsedArgs {
13
13
  * them. `-h` is already honoured as a bare top-level token; without this it
14
14
  * fell through to a positional, so `crons -h` reported "Unknown command". */
15
15
  export declare const SHORT_ALIASES: Readonly<Record<string, string>>;
16
+ /**
17
+ * Names in `flags` that `known` does not declare.
18
+ *
19
+ * The parser has no schema, so an unrecognised flag parses fine and is then
20
+ * simply ignored — `app install foo --evn KEY=V` sent no env vars at all and
21
+ * exited 0, as though it had worked. Every command that builds its request or
22
+ * its behaviour from named flags therefore has to reject what it does not know
23
+ * rather than drop it; this is the one place that decides what "does not know"
24
+ * means, so the answer cannot drift between commands.
25
+ */
26
+ export declare function unknownFlagNames(flags: Record<string, string | boolean>, known: ReadonlySet<string>): string[];
27
+ /**
28
+ * Split a `--flag K=V[,K=V...]` value into its pairs, or return null after
29
+ * writing the reason to stderr.
30
+ *
31
+ * Only the list shape is decided here — splitting on commas, ignoring empty
32
+ * entries, and requiring a non-empty name before the first `=`. What a valid
33
+ * name or value *is* differs per flag (`service --env` bans the variables the
34
+ * installer sets, `app --ports` wants a port number), so each caller validates
35
+ * its own pairs; this exists so they cannot disagree about the shape itself, or
36
+ * about how a missing value is reported.
37
+ *
38
+ * `raw` is `boolean` when the flag was passed with nothing after it — the
39
+ * schema-less parser reads `--env` at the end of a line as `true`. That is
40
+ * rejected rather than read as "not passed": silently treating it as omitted
41
+ * installs with none of the environment the caller believes they gave.
42
+ *
43
+ * `pairShape` names the expected form in the error message ("KEY=VALUE",
44
+ * "NAME=PORT").
45
+ */
46
+ export declare function parseKeyValueList(flagName: string, raw: string | boolean | undefined, pairShape: string): Array<{
47
+ key: string;
48
+ value: string;
49
+ }> | null;
16
50
  /**
17
51
  * `booleanFlags` names flags that must never consume the next token as a value
18
52
  * (e.g. `--force <positional>` should leave `<positional>` alone). Without this,
@@ -1 +1 @@
1
- {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;CACzC;AAED;;8EAE8E;AAC9E,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAA+B,CAAC;AAU3F;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,YAAY,GAAE,WAAW,CAAC,MAAM,CAAa,GAAG,UAAU,CA8BxG"}
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;CACzC;AAED;;8EAE8E;AAC9E,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAA+B,CAAC;AAU3F;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,EAAE,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAE9G;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,EACjC,SAAS,EAAE,MAAM,GAChB,KAAK,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,IAAI,CAkB9C;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,YAAY,GAAE,WAAW,CAAC,MAAM,CAAa,GAAG,UAAU,CA8BxG"}
package/dist/cli/args.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SHORT_ALIASES = void 0;
4
+ exports.unknownFlagNames = unknownFlagNames;
5
+ exports.parseKeyValueList = parseKeyValueList;
4
6
  exports.parseCliArgs = parseCliArgs;
5
7
  /** Single-dash aliases, expanded to the long name before anything else sees
6
8
  * them. `-h` is already honoured as a bare top-level token; without this it
@@ -13,6 +15,59 @@ exports.SHORT_ALIASES = { h: 'help', V: 'version' };
13
15
  function looksLikeFlag(token) {
14
16
  return token.startsWith('--') || (token.length === 2 && token[0] === '-' && token[1] in exports.SHORT_ALIASES);
15
17
  }
18
+ /**
19
+ * Names in `flags` that `known` does not declare.
20
+ *
21
+ * The parser has no schema, so an unrecognised flag parses fine and is then
22
+ * simply ignored — `app install foo --evn KEY=V` sent no env vars at all and
23
+ * exited 0, as though it had worked. Every command that builds its request or
24
+ * its behaviour from named flags therefore has to reject what it does not know
25
+ * rather than drop it; this is the one place that decides what "does not know"
26
+ * means, so the answer cannot drift between commands.
27
+ */
28
+ function unknownFlagNames(flags, known) {
29
+ return Object.keys(flags).filter((name) => !known.has(name));
30
+ }
31
+ /**
32
+ * Split a `--flag K=V[,K=V...]` value into its pairs, or return null after
33
+ * writing the reason to stderr.
34
+ *
35
+ * Only the list shape is decided here — splitting on commas, ignoring empty
36
+ * entries, and requiring a non-empty name before the first `=`. What a valid
37
+ * name or value *is* differs per flag (`service --env` bans the variables the
38
+ * installer sets, `app --ports` wants a port number), so each caller validates
39
+ * its own pairs; this exists so they cannot disagree about the shape itself, or
40
+ * about how a missing value is reported.
41
+ *
42
+ * `raw` is `boolean` when the flag was passed with nothing after it — the
43
+ * schema-less parser reads `--env` at the end of a line as `true`. That is
44
+ * rejected rather than read as "not passed": silently treating it as omitted
45
+ * installs with none of the environment the caller believes they gave.
46
+ *
47
+ * `pairShape` names the expected form in the error message ("KEY=VALUE",
48
+ * "NAME=PORT").
49
+ */
50
+ function parseKeyValueList(flagName, raw, pairShape) {
51
+ if (raw === undefined)
52
+ return [];
53
+ if (typeof raw !== 'string' || raw.trim() === '') {
54
+ process.stderr.write(`--${flagName} requires a comma-separated list of ${pairShape} pairs.\n`);
55
+ return null;
56
+ }
57
+ const pairs = [];
58
+ for (const entry of raw.split(',')) {
59
+ const trimmed = entry.trim();
60
+ if (!trimmed)
61
+ continue;
62
+ const eq = trimmed.indexOf('=');
63
+ if (eq <= 0) {
64
+ process.stderr.write(`Invalid --${flagName} entry "${trimmed}" — expected ${pairShape}.\n`);
65
+ return null;
66
+ }
67
+ pairs.push({ key: trimmed.slice(0, eq), value: trimmed.slice(eq + 1) });
68
+ }
69
+ return pairs;
70
+ }
16
71
  /**
17
72
  * `booleanFlags` names flags that must never consume the next token as a value
18
73
  * (e.g. `--force <positional>` should leave `<positional>` alone). Without this,
@@ -1 +1 @@
1
- {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":";;;AAuCA,oCA8BC;AAzDD;;8EAE8E;AACjE,QAAA,aAAa,GAAqC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC;AAE3F;;;gEAGgE;AAChE,SAAS,aAAa,CAAC,KAAa;IAClC,OAAO,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,qBAAa,CAAC,CAAC;AACzG,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,SAAgB,YAAY,CAAC,MAAgB,EAAE,eAAoC,IAAI,GAAG,EAAE;IAC1F,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,KAAK,GAAqC,EAAE,CAAC;IACnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,qBAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/E,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;gBACd,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;gBACjC,KAAK,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;gBAC/D,SAAS;YACX,CAAC;YACD,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3B,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;gBACnB,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3B,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC/C,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;gBACnB,CAAC,EAAE,CAAC;YACN,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;YACrB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;AAChC,CAAC"}
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"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"}