@diegoaltoworks/chatter 0.61.0 → 0.63.0

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 (125) hide show
  1. package/README.md +55 -17
  2. package/dist/auth/secretStrength.d.ts +1 -1
  3. package/dist/auth/secretStrength.d.ts.map +1 -1
  4. package/dist/bin/create-apikey.js +45 -28
  5. package/dist/bin/wa-pair.js +113 -46
  6. package/dist/channels/backoff.d.ts +10 -0
  7. package/dist/channels/backoff.d.ts.map +1 -0
  8. package/dist/channels/gates.d.ts +15 -8
  9. package/dist/channels/gates.d.ts.map +1 -1
  10. package/dist/channels/index.d.ts +1 -1
  11. package/dist/channels/index.d.ts.map +1 -1
  12. package/dist/channels/index.js +38 -5
  13. package/dist/channels/index.mjs +37 -5
  14. package/dist/channels/matrix/api.d.ts +25 -6
  15. package/dist/channels/matrix/api.d.ts.map +1 -1
  16. package/dist/channels/matrix/channel.d.ts +4 -56
  17. package/dist/channels/matrix/channel.d.ts.map +1 -1
  18. package/dist/channels/matrix/handler.d.ts +16 -9
  19. package/dist/channels/matrix/handler.d.ts.map +1 -1
  20. package/dist/channels/matrix/index.d.ts +2 -2
  21. package/dist/channels/matrix/index.d.ts.map +1 -1
  22. package/dist/channels/matrix/index.js +196 -40
  23. package/dist/channels/matrix/index.mjs +189 -39
  24. package/dist/channels/matrix/sync.d.ts +4 -1
  25. package/dist/channels/matrix/sync.d.ts.map +1 -1
  26. package/dist/channels/matrix/updates.d.ts +39 -10
  27. package/dist/channels/matrix/updates.d.ts.map +1 -1
  28. package/dist/channels/pipeline.d.ts +14 -14
  29. package/dist/channels/pipeline.d.ts.map +1 -1
  30. package/dist/channels/polling.d.ts +67 -0
  31. package/dist/channels/polling.d.ts.map +1 -0
  32. package/dist/channels/telegram/channel.d.ts +2 -56
  33. package/dist/channels/telegram/channel.d.ts.map +1 -1
  34. package/dist/channels/telegram/index.js +53 -23
  35. package/dist/channels/telegram/index.mjs +53 -23
  36. package/dist/channels/telegram/poll.d.ts +5 -1
  37. package/dist/channels/telegram/poll.d.ts.map +1 -1
  38. package/dist/channels/telegram/webhook.d.ts +2 -10
  39. package/dist/channels/telegram/webhook.d.ts.map +1 -1
  40. package/dist/channels/whatsapp/authState.d.ts +40 -9
  41. package/dist/channels/whatsapp/authState.d.ts.map +1 -1
  42. package/dist/channels/whatsapp/channel.d.ts +39 -4
  43. package/dist/channels/whatsapp/channel.d.ts.map +1 -1
  44. package/dist/channels/whatsapp/inbound.d.ts +23 -25
  45. package/dist/channels/whatsapp/inbound.d.ts.map +1 -1
  46. package/dist/channels/whatsapp/index.d.ts +3 -3
  47. package/dist/channels/whatsapp/index.d.ts.map +1 -1
  48. package/dist/channels/whatsapp/index.js +188 -66
  49. package/dist/channels/whatsapp/index.mjs +185 -66
  50. package/dist/channels/whatsapp/pairing.d.ts.map +1 -1
  51. package/dist/client/src/Chat.d.ts.map +1 -1
  52. package/dist/client/src/ChatButton.d.ts.map +1 -1
  53. package/dist/client/src/browser.d.ts.map +1 -1
  54. package/dist/core/answer.d.ts +8 -0
  55. package/dist/core/answer.d.ts.map +1 -1
  56. package/dist/core/buildLock.d.ts +52 -0
  57. package/dist/core/buildLock.d.ts.map +1 -0
  58. package/dist/core/pipeline.d.ts +42 -5
  59. package/dist/core/pipeline.d.ts.map +1 -1
  60. package/dist/core/prompts.d.ts +2 -0
  61. package/dist/core/prompts.d.ts.map +1 -1
  62. package/dist/core/retrieval.d.ts +100 -25
  63. package/dist/core/retrieval.d.ts.map +1 -1
  64. package/dist/core/session.d.ts +8 -0
  65. package/dist/core/session.d.ts.map +1 -1
  66. package/dist/flows/index.d.ts +1 -1
  67. package/dist/flows/index.d.ts.map +1 -1
  68. package/dist/flows/index.js +8 -6
  69. package/dist/flows/index.mjs +7 -6
  70. package/dist/flows/intent.d.ts +1 -1
  71. package/dist/flows/intent.d.ts.map +1 -1
  72. package/dist/flows/loader.d.ts +1 -2
  73. package/dist/flows/loader.d.ts.map +1 -1
  74. package/dist/flows/params.d.ts +1 -1
  75. package/dist/flows/params.d.ts.map +1 -1
  76. package/dist/flows/session.d.ts +4 -2
  77. package/dist/flows/session.d.ts.map +1 -1
  78. package/dist/flows/types.d.ts +2 -3
  79. package/dist/flows/types.d.ts.map +1 -1
  80. package/dist/history/index.d.ts +1 -1
  81. package/dist/history/index.d.ts.map +1 -1
  82. package/dist/history/index.js +6 -3
  83. package/dist/history/index.mjs +5 -3
  84. package/dist/history/tursoStore.d.ts +6 -4
  85. package/dist/history/tursoStore.d.ts.map +1 -1
  86. package/dist/images/index.js +2 -2
  87. package/dist/images/index.mjs +2 -2
  88. package/dist/index.d.ts +7 -5
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +596 -295
  91. package/dist/index.mjs +595 -295
  92. package/dist/mcp-server/index.d.ts +5 -5
  93. package/dist/mcp-server/index.d.ts.map +1 -1
  94. package/dist/mcp-server/logger.d.ts +12 -2
  95. package/dist/mcp-server/logger.d.ts.map +1 -1
  96. package/dist/mcp-server/rate-limiter.d.ts +10 -3
  97. package/dist/mcp-server/rate-limiter.d.ts.map +1 -1
  98. package/dist/mcp-server/types.d.ts +20 -5
  99. package/dist/mcp-server/types.d.ts.map +1 -1
  100. package/dist/mcp-server.js +479 -244
  101. package/dist/mcp-server.mjs +485 -244
  102. package/dist/middleware/apiKey.d.ts +10 -0
  103. package/dist/middleware/apiKey.d.ts.map +1 -0
  104. package/dist/middleware/auth.d.ts.map +1 -1
  105. package/dist/middleware/cors.d.ts.map +1 -1
  106. package/dist/middleware/ratelimit.d.ts.map +1 -1
  107. package/dist/middleware/referrer.d.ts.map +1 -1
  108. package/dist/middleware/session.d.ts +1 -1
  109. package/dist/middleware/session.d.ts.map +1 -1
  110. package/dist/routes/demo.d.ts.map +1 -1
  111. package/dist/routes/openai.d.ts.map +1 -1
  112. package/dist/routes/private.d.ts.map +1 -1
  113. package/dist/routes/public.d.ts.map +1 -1
  114. package/dist/scheduler/index.js +1 -1
  115. package/dist/scheduler/index.mjs +1 -1
  116. package/dist/scheduler/scheduler.d.ts +9 -1
  117. package/dist/scheduler/scheduler.d.ts.map +1 -1
  118. package/dist/server.d.ts.map +1 -1
  119. package/dist/server.js +491 -268
  120. package/dist/server.mjs +497 -268
  121. package/dist/shortener.d.ts +5 -4
  122. package/dist/shortener.d.ts.map +1 -1
  123. package/dist/types.d.ts +129 -75
  124. package/dist/types.d.ts.map +1 -1
  125. package/package.json +6 -3
@@ -35,6 +35,7 @@ __export(index_exports, {
35
35
  LEASE_WAIT_MS: () => LEASE_WAIT_MS,
36
36
  acquireSessionLease: () => acquireSessionLease,
37
37
  canAcquireLease: () => canAcquireLease,
38
+ createTursoWaAuthKV: () => createTursoWaAuthKV,
38
39
  createTursoWaLeaseStore: () => createTursoWaLeaseStore,
39
40
  createWhatsAppChannel: () => createWhatsAppChannel,
40
41
  createWhatsAppImageHandler: () => createWhatsAppImageHandler,
@@ -50,12 +51,14 @@ __export(index_exports, {
50
51
  jidsMatch: () => jidsMatch,
51
52
  loadBaileys: () => loadBaileys,
52
53
  messageContext: () => messageContext,
54
+ normalizeWaMediaPayload: () => normalizeWaMediaPayload,
53
55
  reconnectDelayMs: () => reconnectDelayMs,
54
56
  resolveWaMessage: () => resolveWaMessage,
55
57
  senderNameFor: () => senderNameFor,
56
58
  senderPhoneFor: () => senderPhoneFor,
57
59
  shutdownWaSessions: () => shutdownWaSessions,
58
60
  stripOwnMentions: () => stripOwnMentions,
61
+ useAuthState: () => useAuthState,
59
62
  useTursoAuthState: () => useTursoAuthState
60
63
  });
61
64
  module.exports = __toCommonJS(index_exports);
@@ -129,27 +132,55 @@ function ensureAuthSchema(client) {
129
132
  }
130
133
  return ready;
131
134
  }
132
- async function useTursoAuthState(db, secret, sessionId, runtime) {
133
- await ensureAuthSchema(db);
135
+ function createTursoWaAuthKV(db) {
136
+ return {
137
+ async read(id) {
138
+ await ensureAuthSchema(db);
139
+ const result = await db.execute({
140
+ sql: "SELECT value FROM wa_auth WHERE id = ?",
141
+ args: [id]
142
+ });
143
+ const row = result.rows[0];
144
+ return row ? row.value : null;
145
+ },
146
+ async write(id, value) {
147
+ await ensureAuthSchema(db);
148
+ await db.execute({
149
+ sql: "INSERT OR REPLACE INTO wa_auth (id, value) VALUES (?, ?)",
150
+ args: [id, value]
151
+ });
152
+ },
153
+ async remove(id) {
154
+ await ensureAuthSchema(db);
155
+ await db.execute({ sql: "DELETE FROM wa_auth WHERE id = ?", args: [id] });
156
+ },
157
+ // Mirrors isAuthRowForSession - keep the two in sync.
158
+ async clear(sessionId) {
159
+ await ensureAuthSchema(db);
160
+ if (sessionId === "default") {
161
+ await db.execute("DELETE FROM wa_auth WHERE id NOT LIKE 's:%'");
162
+ } else {
163
+ await db.execute({
164
+ sql: "DELETE FROM wa_auth WHERE id LIKE ?",
165
+ args: [`s:${sessionId}/%`]
166
+ });
167
+ }
168
+ }
169
+ };
170
+ }
171
+ async function useAuthState(store, secret, sessionId, runtime) {
134
172
  const rowId = (id) => sessionId === "default" ? id : `s:${sessionId}/${id}`;
135
173
  async function read(id) {
136
- const result = await db.execute({
137
- sql: "SELECT value FROM wa_auth WHERE id = ?",
138
- args: [rowId(id)]
139
- });
140
- const row = result.rows[0];
141
- if (!row) return null;
142
- return JSON.parse(await decrypt(row.value, secret), runtime.bufferJSON.reviver);
174
+ const value = await store.read(rowId(id));
175
+ if (value === null) return null;
176
+ return JSON.parse(await decrypt(value, secret), runtime.bufferJSON.reviver);
143
177
  }
144
178
  async function write(id, value) {
145
179
  const payload = await encrypt(JSON.stringify(value, runtime.bufferJSON.replacer), secret);
146
- await db.execute({
147
- sql: "INSERT OR REPLACE INTO wa_auth (id, value) VALUES (?, ?)",
148
- args: [rowId(id), payload]
149
- });
180
+ await store.write(rowId(id), payload);
150
181
  }
151
182
  async function remove(id) {
152
- await db.execute({ sql: "DELETE FROM wa_auth WHERE id = ?", args: [rowId(id)] });
183
+ await store.remove(rowId(id));
153
184
  }
154
185
  const creds = await read("creds") ?? runtime.initAuthCreds();
155
186
  return {
@@ -186,18 +217,13 @@ async function useTursoAuthState(db, secret, sessionId, runtime) {
186
217
  }
187
218
  },
188
219
  saveCreds: () => write("creds", creds),
189
- clear: async () => {
190
- if (sessionId === "default") {
191
- await db.execute("DELETE FROM wa_auth WHERE id NOT LIKE 's:%'");
192
- } else {
193
- await db.execute({
194
- sql: "DELETE FROM wa_auth WHERE id LIKE ?",
195
- args: [`s:${sessionId}/%`]
196
- });
197
- }
198
- }
220
+ // Scoped to THIS session - never nuke a sibling number's pairing.
221
+ clear: () => store.clear(sessionId)
199
222
  };
200
223
  }
224
+ async function useTursoAuthState(db, secret, sessionId, runtime) {
225
+ return useAuthState(createTursoWaAuthKV(db), secret, sessionId, runtime);
226
+ }
201
227
 
202
228
  // src/channels/whatsapp/baileys.ts
203
229
  function wrapMissingBaileysError(cause) {
@@ -260,6 +286,11 @@ function createConsoleLogger(level = "info") {
260
286
  };
261
287
  }
262
288
 
289
+ // src/channels/backoff.ts
290
+ function exponentialBackoffMs(baseMs, maxMs, n) {
291
+ return Math.min(baseMs * 2 ** Math.max(0, Math.min(n, 20)), maxMs);
292
+ }
293
+
263
294
  // src/channels/whatsapp/lease.ts
264
295
  var LEASE_STALE_MS = 9e4;
265
296
  var LEASE_HEARTBEAT_MS = 3e4;
@@ -318,7 +349,7 @@ function createTursoWaLeaseStore(client) {
318
349
  var RECONNECT_DELAY_MS = 5e3;
319
350
  var RECONNECT_MAX_DELAY_MS = 10 * 60 * 1e3;
320
351
  function reconnectDelayMs(failures) {
321
- return Math.min(RECONNECT_DELAY_MS * 2 ** Math.min(failures, 20), RECONNECT_MAX_DELAY_MS);
352
+ return exponentialBackoffMs(RECONNECT_DELAY_MS, RECONNECT_MAX_DELAY_MS, failures);
322
353
  }
323
354
  async function shutdownWaSessions(sessions) {
324
355
  await Promise.all(
@@ -329,11 +360,17 @@ async function shutdownWaSessions(sessions) {
329
360
  })
330
361
  );
331
362
  }
332
- async function loseSession(sessionId, sessions) {
363
+ function claimSession(sessionId, sessions) {
333
364
  const handle = sessions.get(sessionId);
334
- if (!handle) return;
365
+ if (!handle) return void 0;
335
366
  sessions.delete(sessionId);
367
+ return handle;
368
+ }
369
+ async function loseSession(sessionId, sessions) {
370
+ const handle = claimSession(sessionId, sessions);
371
+ if (!handle) return false;
336
372
  await shutdownWaSessions(/* @__PURE__ */ new Map([[sessionId, handle]]));
373
+ return true;
337
374
  }
338
375
  async function acquireSessionLease(sessionId, deps) {
339
376
  const { leaseStore, instanceId, sessions, isStopped, connect } = deps;
@@ -369,7 +406,9 @@ async function acquireSessionLease(sessionId, deps) {
369
406
  void leaseStore.tryAcquire(sessionId, instanceId, now(), staleMs).then((stillHeld) => {
370
407
  if (stillHeld || isStopped()) return;
371
408
  logger.error(`WhatsApp[${sessionId}]: lost the lease to another instance \u2014 disconnecting`);
372
- void loseSession(sessionId, sessions).then(retry);
409
+ void loseSession(sessionId, sessions).then((lost) => {
410
+ if (lost) retry();
411
+ });
373
412
  }).catch((error) => logger.warn(`WhatsApp[${sessionId}]: lease heartbeat failed:`, error));
374
413
  }, heartbeatMs);
375
414
  if (typeof heartbeat === "object" && "unref" in heartbeat) heartbeat.unref();
@@ -381,6 +420,10 @@ async function acquireSessionLease(sessionId, deps) {
381
420
  try {
382
421
  await connect(sessionId);
383
422
  } catch (error) {
423
+ if (deps.onConnectFailed) {
424
+ deps.onConnectFailed(sessionId, error);
425
+ return;
426
+ }
384
427
  logger.error(`WhatsApp[${sessionId}]: connect failed right after acquiring the lease:`, error);
385
428
  await loseSession(sessionId, sessions);
386
429
  retry();
@@ -389,9 +432,18 @@ async function acquireSessionLease(sessionId, deps) {
389
432
  function senderNameFor(channelName, sessionId) {
390
433
  return sessionId === "default" ? channelName : `${channelName}:${sessionId}`;
391
434
  }
435
+ function normalizeWaMediaPayload(payload) {
436
+ const media = typeof payload === "string" ? { url: payload } : payload;
437
+ if (!media || typeof media !== "object" || typeof media.url !== "string" || !media.url) {
438
+ throw new TypeError(
439
+ `WhatsApp sendMedia payload must be a URL string or { url, caption? }, got: ${typeof payload}`
440
+ );
441
+ }
442
+ return media;
443
+ }
392
444
  function createWhatsAppChannel(config) {
393
445
  assertStrongSecret(config.sessionSecret, "WhatsApp sessionSecret (e.g. WA_SESSION_SECRET)");
394
- const channelName = "whatsapp";
446
+ const channelName = config.name ?? "whatsapp";
395
447
  const sessionIds = config.sessionIds && config.sessionIds.length > 0 ? config.sessionIds : ["default"];
396
448
  const now = config.now ?? Date.now;
397
449
  const schedule = config.schedule ?? ((fn, ms) => void setTimeout(fn, ms));
@@ -409,10 +461,59 @@ function createWhatsAppChannel(config) {
409
461
  name: channelName,
410
462
  async start(deps) {
411
463
  const log = config.logger ?? deps.logger ?? createConsoleLogger();
412
- const db = deps.db;
413
- const leaseStore = createTursoWaLeaseStore(db);
464
+ if ((!config.leaseStore || !config.authStore) && !deps.db) {
465
+ throw new Error(
466
+ "The WhatsApp channel needs a libsql client for auth state and the session lease: set config.database (config.retriever alone does not open one), or supply both config.leaseStore and config.authStore."
467
+ );
468
+ }
469
+ const leaseStore = config.leaseStore ?? createTursoWaLeaseStore(deps.db);
470
+ const authStore = config.authStore ?? createTursoWaAuthKV(deps.db);
414
471
  senders = deps.senders;
415
472
  await loadBaileys2();
473
+ const leaseDeps = {
474
+ leaseStore,
475
+ instanceId,
476
+ sessions,
477
+ isStopped: () => stopped,
478
+ connect,
479
+ now,
480
+ staleMs,
481
+ heartbeatMs,
482
+ waitMs,
483
+ schedule,
484
+ logger: log,
485
+ onConnectFailed: recoverFromFailedConnect
486
+ };
487
+ function nextRetryDelayMs(sessionId) {
488
+ const failures = failureCounts.get(sessionId) ?? 0;
489
+ failureCounts.set(sessionId, failures + 1);
490
+ return reconnectDelayMs(failures);
491
+ }
492
+ function attemptConnect(sessionId) {
493
+ void connect(sessionId).catch((error) => recoverFromFailedConnect(sessionId, error));
494
+ }
495
+ function recoverFromFailedConnect(sessionId, error) {
496
+ if (stopped) return;
497
+ const handle = claimSession(sessionId, sessions);
498
+ if (!handle) return;
499
+ const delay = nextRetryDelayMs(sessionId);
500
+ log.error(
501
+ `WhatsApp[${sessionId}]: connect attempt failed, releasing the lease and retrying in ${Math.round(delay / 1e3)}s:`,
502
+ error
503
+ );
504
+ schedule(
505
+ () => void acquireSessionLease(sessionId, leaseDeps).catch(
506
+ (retryError) => log.error(`WhatsApp[${sessionId}]: lease re-check failed:`, retryError)
507
+ ),
508
+ delay
509
+ );
510
+ void shutdownWaSessions(/* @__PURE__ */ new Map([[sessionId, handle]])).catch(
511
+ (teardownError) => log.warn(
512
+ `WhatsApp[${sessionId}]: teardown after a failed connect failed:`,
513
+ teardownError
514
+ )
515
+ );
516
+ }
416
517
  async function connect(sessionId) {
417
518
  if (stopped || !sessions.has(sessionId)) return;
418
519
  const baileys = await loadBaileys2();
@@ -421,8 +522,8 @@ function createWhatsAppChannel(config) {
421
522
  initAuthCreds: baileys.initAuthCreds,
422
523
  appStateSyncKeyFromObject: (value) => baileys.proto.Message.AppStateSyncKeyData.fromObject(value)
423
524
  };
424
- const { state, saveCreds } = await useTursoAuthState(
425
- db,
525
+ const { state, saveCreds } = await useAuthState(
526
+ authStore,
426
527
  config.sessionSecret,
427
528
  sessionId,
428
529
  runtime
@@ -431,7 +532,7 @@ function createWhatsAppChannel(config) {
431
532
  log.warn(
432
533
  `WhatsApp[${sessionId}]: no paired session found \u2014 pair it first (see docs/channels.md). Re-checking in 60s.`
433
534
  );
434
- schedule(() => void connect(sessionId).catch(() => void 0), 6e4);
535
+ schedule(() => attemptConnect(sessionId), 6e4);
435
536
  return;
436
537
  }
437
538
  const { version } = await baileys.fetchLatestBaileysVersion();
@@ -466,6 +567,13 @@ function createWhatsAppChannel(config) {
466
567
  sendText: async (chatId, text) => {
467
568
  await sock.sendMessage(chatId, { text });
468
569
  },
570
+ sendMedia: async (chatId, payload) => {
571
+ const { url, caption } = normalizeWaMediaPayload(payload);
572
+ await sock.sendMessage(
573
+ chatId,
574
+ caption !== void 0 ? { image: { url }, caption } : { image: { url } }
575
+ );
576
+ },
469
577
  sendReaction: async (chatId, messageRef, emoji) => {
470
578
  await sock.sendMessage(chatId, {
471
579
  react: { text: emoji, key: messageRef }
@@ -490,17 +598,11 @@ function createWhatsAppChannel(config) {
490
598
  return;
491
599
  }
492
600
  if (!stopped && sessions.has(sessionId)) {
493
- const delay = reconnectDelayMs(failureCounts.get(sessionId) ?? 0);
494
- failureCounts.set(sessionId, (failureCounts.get(sessionId) ?? 0) + 1);
601
+ const delay = nextRetryDelayMs(sessionId);
495
602
  log.warn(
496
603
  `WhatsApp[${sessionId}]: connection closed (${statusCode ?? "?"}), reconnecting in ${Math.round(delay / 1e3)}s...`
497
604
  );
498
- schedule(
499
- () => void connect(sessionId).catch(
500
- (error) => log.error(`WhatsApp[${sessionId}]: reconnect failed:`, error)
501
- ),
502
- delay
503
- );
605
+ schedule(() => attemptConnect(sessionId), delay);
504
606
  }
505
607
  }
506
608
  });
@@ -518,19 +620,9 @@ function createWhatsAppChannel(config) {
518
620
  }
519
621
  }
520
622
  for (const sessionId of sessionIds) {
521
- void acquireSessionLease(sessionId, {
522
- leaseStore,
523
- instanceId,
524
- sessions,
525
- isStopped: () => stopped,
526
- connect,
527
- now,
528
- staleMs,
529
- heartbeatMs,
530
- waitMs,
531
- schedule,
532
- logger: log
533
- }).catch((error) => log.error(`WhatsApp[${sessionId}]: failed to start:`, error));
623
+ void acquireSessionLease(sessionId, leaseDeps).catch(
624
+ (error) => log.error(`WhatsApp[${sessionId}]: failed to start:`, error)
625
+ );
534
626
  }
535
627
  },
536
628
  async stop() {
@@ -741,7 +833,8 @@ async function answerOnce({
741
833
  async function applyTransformReply(transformReply, input, logger) {
742
834
  if (!transformReply) return input.text;
743
835
  try {
744
- return await transformReply(input);
836
+ const result = await transformReply(input);
837
+ return typeof result === "string" ? result : null;
745
838
  } catch (error) {
746
839
  logger?.error(
747
840
  `transformReply threw for channel "${input.channel}"; sending the original reply`,
@@ -803,6 +896,7 @@ async function prepareChat({
803
896
  sender,
804
897
  rewriteQuery,
805
898
  rerankContext,
899
+ fallbackFn,
806
900
  logger
807
901
  }) {
808
902
  const lastUserMsg = lastUserMessage(messages);
@@ -819,21 +913,39 @@ async function prepareChat({
819
913
  logger?.error("rewriteQuery threw; falling back to the original query", error);
820
914
  }
821
915
  }
822
- let ctx = await store.query(query, topK, buckets ?? defaultBuckets(mode));
916
+ const resolvedBuckets = buckets ?? defaultBuckets(mode);
917
+ const retrievedChunks = await store.query(query, topK, resolvedBuckets);
918
+ let ctx = retrievedChunks;
823
919
  if (rerankContext) {
824
920
  try {
825
- const reranked = await rerankContext({ query, chunks: ctx });
921
+ const reranked = await rerankContext({ query, chunks: [...ctx] });
826
922
  if (Array.isArray(reranked) && reranked.every((c) => typeof c === "string")) ctx = reranked;
827
923
  } catch (error) {
828
924
  logger?.error("rerankContext threw; falling back to the original chunks", error);
829
925
  }
830
926
  }
927
+ let fallback;
928
+ if (ctx.length === 0 && fallbackFn) {
929
+ try {
930
+ const guidance = await fallbackFn({
931
+ query,
932
+ mode,
933
+ buckets: resolvedBuckets,
934
+ sender,
935
+ retrievedChunks
936
+ });
937
+ if (typeof guidance === "string" && guidance.trim()) fallback = guidance.trim();
938
+ } catch (error) {
939
+ logger?.error("fallbackFn threw; proceeding without fallback guidance", error);
940
+ }
941
+ }
831
942
  const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
832
943
  const hint = channelHint?.trim();
833
944
  const system = [
834
945
  prompts.baseSystemRules,
835
946
  persona,
836
947
  ...hint ? [hint] : [],
948
+ ...fallback ? [fallback] : [],
837
949
  `${label}:
838
950
  ${ctx.join("\n\n")}`
839
951
  ].join("\n\n");
@@ -841,7 +953,7 @@ ${ctx.join("\n\n")}`
841
953
  }
842
954
 
843
955
  // src/history/compaction.ts
844
- var DEFAULT_TIMEOUT_MS = 8e3;
956
+ var COMPACTION_TIMEOUT_MS = 8e3;
845
957
  var LOAD_ALL = Number.MAX_SAFE_INTEGER;
846
958
  var SUMMARY_PREFIX = "Summary of earlier conversation: ";
847
959
  function defaultSummarize(client, model) {
@@ -869,7 +981,7 @@ function createHistoryCompactor(deps, options) {
869
981
  throw new Error(`Invalid history compaction keep: ${keep}`);
870
982
  }
871
983
  const summarize = options.summarize ?? defaultSummarize(deps.client, options.model);
872
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
984
+ const timeoutMs = options.timeoutMs ?? COMPACTION_TIMEOUT_MS;
873
985
  return {
874
986
  async maybeCompact(store, conversationId) {
875
987
  const turns = await store.load(conversationId, LOAD_ALL);
@@ -910,6 +1022,16 @@ function createHistoryCompactor(deps, options) {
910
1022
  }
911
1023
 
912
1024
  // src/channels/pipeline.ts
1025
+ function resolveBrainHooks(config, fallback) {
1026
+ return {
1027
+ answerFn: config.answerFn ?? fallback?.answerFn,
1028
+ bucketsFor: config.bucketsFor ?? fallback?.bucketsFor,
1029
+ rewriteQuery: config.rewriteQuery ?? fallback?.rewriteQuery,
1030
+ rerankContext: config.rerankContext ?? fallback?.rerankContext,
1031
+ fallbackFn: config.fallbackFn ?? fallback?.fallbackFn,
1032
+ transformReply: config.transformReply ?? fallback?.transformReply
1033
+ };
1034
+ }
913
1035
  var DEFAULT_HISTORY_LIMIT = 20;
914
1036
  var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
915
1037
  var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
@@ -994,6 +1116,7 @@ function createInboundPipeline(deps, config) {
994
1116
  sender,
995
1117
  rewriteQuery: config.rewriteQuery,
996
1118
  rerankContext: config.rerankContext,
1119
+ fallbackFn: config.fallbackFn,
997
1120
  logger: config.logger
998
1121
  });
999
1122
  const { content: produced } = await answerOnce({
@@ -1129,13 +1252,9 @@ function createWhatsAppInboundHandler(config) {
1129
1252
  { client: config.client, store: config.store, prompts: config.prompts },
1130
1253
  {
1131
1254
  channel: "whatsapp",
1132
- answerFn: config.answerFn,
1133
- bucketsFor: config.bucketsFor,
1134
- rewriteQuery: config.rewriteQuery,
1135
- rerankContext: config.rerankContext,
1136
- transformReply: config.transformReply,
1255
+ ...resolveBrainHooks(config, config.serverConfig),
1137
1256
  model: config.model,
1138
- channelHint: config.channelHint,
1257
+ channelHint: config.channelHint ?? "Channel: WhatsApp.",
1139
1258
  personaResolver: personaResolver ? ({ sender, text }) => personaResolver({ senderPhone: sender, text }) : void 0,
1140
1259
  history: config.history,
1141
1260
  allowedChats,
@@ -1296,6 +1415,7 @@ function createWhatsAppMessageRouter(config) {
1296
1415
  LEASE_WAIT_MS,
1297
1416
  acquireSessionLease,
1298
1417
  canAcquireLease,
1418
+ createTursoWaAuthKV,
1299
1419
  createTursoWaLeaseStore,
1300
1420
  createWhatsAppChannel,
1301
1421
  createWhatsAppImageHandler,
@@ -1311,11 +1431,13 @@ function createWhatsAppMessageRouter(config) {
1311
1431
  jidsMatch,
1312
1432
  loadBaileys,
1313
1433
  messageContext,
1434
+ normalizeWaMediaPayload,
1314
1435
  reconnectDelayMs,
1315
1436
  resolveWaMessage,
1316
1437
  senderNameFor,
1317
1438
  senderPhoneFor,
1318
1439
  shutdownWaSessions,
1319
1440
  stripOwnMentions,
1441
+ useAuthState,
1320
1442
  useTursoAuthState
1321
1443
  });