@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
@@ -20,12 +20,16 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/channels/matrix/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ DEFAULT_MAX_MEDIA_BYTES: () => DEFAULT_MAX_MEDIA_BYTES,
23
24
  MAX_TRACKED_SENT_EVENTS: () => MAX_TRACKED_SENT_EVENTS,
24
25
  MatrixApiError: () => MatrixApiError,
25
26
  createMatrixApi: () => createMatrixApi,
26
27
  createMatrixChannel: () => createMatrixChannel,
27
28
  createMatrixSender: () => createMatrixSender,
28
29
  createMatrixSyncHandler: () => createMatrixSyncHandler,
30
+ directInviteFrom: () => directInviteFrom,
31
+ directMappingFromEvents: () => directMappingFromEvents,
32
+ directMappingRooms: () => directMappingRooms,
29
33
  directRoomIds: () => directRoomIds,
30
34
  isReplyToBot: () => isReplyToBot,
31
35
  matrixSenderKey: () => matrixSenderKey,
@@ -36,7 +40,9 @@ __export(index_exports, {
36
40
  retryDelayMs: () => retryDelayMs,
37
41
  runMatrixSyncLoop: () => runMatrixSyncLoop,
38
42
  syncBackoffMs: () => syncBackoffMs,
39
- toChannelMessage: () => toChannelMessage
43
+ toChannelMessage: () => toChannelMessage,
44
+ toDirectMapping: () => toDirectMapping,
45
+ withDirectRoom: () => withDirectRoom
40
46
  });
41
47
  module.exports = __toCommonJS(index_exports);
42
48
 
@@ -57,6 +63,7 @@ function redactToken(text, token) {
57
63
  if (!token) return text;
58
64
  return text.split(token).join("***");
59
65
  }
66
+ var DEFAULT_MAX_MEDIA_BYTES = 25 * 1024 * 1024;
60
67
  var MEDIA_MSGTYPE = {
61
68
  image: "m.image",
62
69
  file: "m.file",
@@ -86,6 +93,55 @@ function toMediaRequest(payload) {
86
93
  }
87
94
  return { kind, url: media.url, caption: media.caption, filename: media.filename };
88
95
  }
96
+ function assertHttpsMediaUrl(url) {
97
+ let parsed;
98
+ try {
99
+ parsed = new URL(url);
100
+ } catch {
101
+ throw new TypeError(
102
+ `Matrix sendMedia url must be an mxc:// URI or an https URL - could not parse "${url}"`
103
+ );
104
+ }
105
+ if (parsed.protocol !== "https:") {
106
+ throw new TypeError(
107
+ `Matrix sendMedia url must be an mxc:// URI or an https URL - got "${parsed.protocol}"`
108
+ );
109
+ }
110
+ }
111
+ async function readCappedBytes(response, maxBytes) {
112
+ const declared = Number(response.headers.get("content-length"));
113
+ if (Number.isFinite(declared) && declared > maxBytes) {
114
+ throw new Error(`media source declares ${declared} bytes, over the ${maxBytes} byte cap`);
115
+ }
116
+ const reader = response.body?.getReader();
117
+ if (!reader) {
118
+ const bytes2 = new Uint8Array(await response.arrayBuffer());
119
+ if (bytes2.byteLength > maxBytes) {
120
+ throw new Error(`media source is ${bytes2.byteLength} bytes, over the ${maxBytes} byte cap`);
121
+ }
122
+ return bytes2;
123
+ }
124
+ const chunks = [];
125
+ let total = 0;
126
+ while (true) {
127
+ const { done, value } = await reader.read();
128
+ if (done) break;
129
+ if (!value) continue;
130
+ total += value.byteLength;
131
+ if (total > maxBytes) {
132
+ await reader.cancel();
133
+ throw new Error(`media source exceeds the ${maxBytes} byte cap`);
134
+ }
135
+ chunks.push(value);
136
+ }
137
+ const bytes = new Uint8Array(total);
138
+ let offset = 0;
139
+ for (const chunk of chunks) {
140
+ bytes.set(chunk, offset);
141
+ offset += chunk.byteLength;
142
+ }
143
+ return bytes;
144
+ }
89
145
  function createMatrixApi(config) {
90
146
  const token = config.accessToken;
91
147
  if (!token.trim()) throw new Error("Matrix accessToken is required (e.g. MATRIX_ACCESS_TOKEN)");
@@ -93,6 +149,7 @@ function createMatrixApi(config) {
93
149
  if (!homeserverUrl)
94
150
  throw new Error("Matrix homeserverUrl is required (e.g. https://matrix.example.org)");
95
151
  const doFetch = config.fetch ?? globalThis.fetch;
152
+ const maxMediaBytes = config.maxMediaBytes ?? DEFAULT_MAX_MEDIA_BYTES;
96
153
  async function call(method, path, options) {
97
154
  const search = new URLSearchParams();
98
155
  for (const [key, value] of Object.entries(options?.query ?? {})) {
@@ -186,13 +243,14 @@ function createMatrixApi(config) {
186
243
  const media = toMediaRequest(payload);
187
244
  let contentUri = media.url;
188
245
  if (!contentUri.startsWith("mxc://")) {
246
+ assertHttpsMediaUrl(media.url);
189
247
  let bytes;
190
248
  let contentType = "application/octet-stream";
191
249
  try {
192
250
  const fetched = await doFetch(media.url);
193
251
  if (!fetched.ok) throw new Error(`fetching media source returned HTTP ${fetched.status}`);
194
252
  contentType = fetched.headers.get("content-type") ?? contentType;
195
- bytes = new Uint8Array(await fetched.arrayBuffer());
253
+ bytes = await readCappedBytes(fetched, maxMediaBytes);
196
254
  } catch (error) {
197
255
  throw new MatrixApiError(`could not fetch media source for upload: ${errorText(error)}`);
198
256
  }
@@ -244,6 +302,13 @@ function createMatrixApi(config) {
244
302
  if (error instanceof MatrixApiError && error.errcode === "M_NOT_FOUND") return void 0;
245
303
  throw error;
246
304
  }
305
+ },
306
+ async setAccountData(userId, type, content) {
307
+ await call(
308
+ "PUT",
309
+ `/_matrix/client/v3/user/${encodeURIComponent(userId)}/account_data/${encodeURIComponent(type)}`,
310
+ { body: content }
311
+ );
247
312
  }
248
313
  };
249
314
  }
@@ -336,7 +401,8 @@ async function answerOnce({
336
401
  async function applyTransformReply(transformReply, input, logger) {
337
402
  if (!transformReply) return input.text;
338
403
  try {
339
- return await transformReply(input);
404
+ const result = await transformReply(input);
405
+ return typeof result === "string" ? result : null;
340
406
  } catch (error) {
341
407
  logger?.error(
342
408
  `transformReply threw for channel "${input.channel}"; sending the original reply`,
@@ -398,6 +464,7 @@ async function prepareChat({
398
464
  sender,
399
465
  rewriteQuery,
400
466
  rerankContext,
467
+ fallbackFn,
401
468
  logger
402
469
  }) {
403
470
  const lastUserMsg = lastUserMessage(messages);
@@ -414,21 +481,39 @@ async function prepareChat({
414
481
  logger?.error("rewriteQuery threw; falling back to the original query", error);
415
482
  }
416
483
  }
417
- let ctx = await store.query(query, topK, buckets ?? defaultBuckets(mode));
484
+ const resolvedBuckets = buckets ?? defaultBuckets(mode);
485
+ const retrievedChunks = await store.query(query, topK, resolvedBuckets);
486
+ let ctx = retrievedChunks;
418
487
  if (rerankContext) {
419
488
  try {
420
- const reranked = await rerankContext({ query, chunks: ctx });
489
+ const reranked = await rerankContext({ query, chunks: [...ctx] });
421
490
  if (Array.isArray(reranked) && reranked.every((c) => typeof c === "string")) ctx = reranked;
422
491
  } catch (error) {
423
492
  logger?.error("rerankContext threw; falling back to the original chunks", error);
424
493
  }
425
494
  }
495
+ let fallback;
496
+ if (ctx.length === 0 && fallbackFn) {
497
+ try {
498
+ const guidance = await fallbackFn({
499
+ query,
500
+ mode,
501
+ buckets: resolvedBuckets,
502
+ sender,
503
+ retrievedChunks
504
+ });
505
+ if (typeof guidance === "string" && guidance.trim()) fallback = guidance.trim();
506
+ } catch (error) {
507
+ logger?.error("fallbackFn threw; proceeding without fallback guidance", error);
508
+ }
509
+ }
426
510
  const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
427
511
  const hint = channelHint?.trim();
428
512
  const system = [
429
513
  prompts.baseSystemRules,
430
514
  persona,
431
515
  ...hint ? [hint] : [],
516
+ ...fallback ? [fallback] : [],
432
517
  `${label}:
433
518
  ${ctx.join("\n\n")}`
434
519
  ].join("\n\n");
@@ -436,7 +521,7 @@ ${ctx.join("\n\n")}`
436
521
  }
437
522
 
438
523
  // src/history/compaction.ts
439
- var DEFAULT_TIMEOUT_MS = 8e3;
524
+ var COMPACTION_TIMEOUT_MS = 8e3;
440
525
  var LOAD_ALL = Number.MAX_SAFE_INTEGER;
441
526
  var SUMMARY_PREFIX = "Summary of earlier conversation: ";
442
527
  function defaultSummarize(client, model) {
@@ -464,7 +549,7 @@ function createHistoryCompactor(deps, options) {
464
549
  throw new Error(`Invalid history compaction keep: ${keep}`);
465
550
  }
466
551
  const summarize = options.summarize ?? defaultSummarize(deps.client, options.model);
467
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
552
+ const timeoutMs = options.timeoutMs ?? COMPACTION_TIMEOUT_MS;
468
553
  return {
469
554
  async maybeCompact(store, conversationId) {
470
555
  const turns = await store.load(conversationId, LOAD_ALL);
@@ -555,6 +640,16 @@ function underReplyRateLimit(msg, limiters) {
555
640
  }
556
641
 
557
642
  // src/channels/pipeline.ts
643
+ function resolveBrainHooks(config, fallback) {
644
+ return {
645
+ answerFn: config.answerFn ?? fallback?.answerFn,
646
+ bucketsFor: config.bucketsFor ?? fallback?.bucketsFor,
647
+ rewriteQuery: config.rewriteQuery ?? fallback?.rewriteQuery,
648
+ rerankContext: config.rerankContext ?? fallback?.rerankContext,
649
+ fallbackFn: config.fallbackFn ?? fallback?.fallbackFn,
650
+ transformReply: config.transformReply ?? fallback?.transformReply
651
+ };
652
+ }
558
653
  var DEFAULT_HISTORY_LIMIT = 20;
559
654
  var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
560
655
  var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
@@ -639,6 +734,7 @@ function createInboundPipeline(deps, config) {
639
734
  sender,
640
735
  rewriteQuery: config.rewriteQuery,
641
736
  rerankContext: config.rerankContext,
737
+ fallbackFn: config.fallbackFn,
642
738
  logger: config.logger
643
739
  });
644
740
  const { content: produced } = await answerOnce({
@@ -675,6 +771,14 @@ function createInboundPipeline(deps, config) {
675
771
  };
676
772
  }
677
773
 
774
+ // src/channels/polling.ts
775
+ function defaultSleep(ms) {
776
+ return new Promise((resolve) => {
777
+ const timer = setTimeout(resolve, ms);
778
+ if (typeof timer === "object" && "unref" in timer) timer.unref();
779
+ });
780
+ }
781
+
678
782
  // src/channels/matrix/updates.ts
679
783
  function messageText(content) {
680
784
  return content.body ?? "";
@@ -710,23 +814,44 @@ function recordSentEventId(sentEventIds, eventId) {
710
814
  if (oldest !== void 0) sentEventIds.delete(oldest);
711
815
  }
712
816
  }
713
- function directRoomIds(accountDataEvents) {
817
+ function toDirectMapping(content) {
818
+ const mapping = {};
819
+ for (const [peer, value] of Object.entries(content ?? {})) {
820
+ if (!Array.isArray(value)) continue;
821
+ const rooms = value.filter((roomId) => typeof roomId === "string");
822
+ if (rooms.length > 0) mapping[peer] = rooms;
823
+ }
824
+ return mapping;
825
+ }
826
+ function directMappingRooms(mapping) {
827
+ return new Set(Object.values(mapping).flat());
828
+ }
829
+ function directMappingFromEvents(accountDataEvents) {
714
830
  const direct = accountDataEvents.find((event) => event.type === "m.direct");
715
- const mapping = direct?.content ?? {};
716
- const rooms = /* @__PURE__ */ new Set();
717
- for (const value of Object.values(mapping)) {
718
- if (Array.isArray(value)) {
719
- for (const roomId of value) {
720
- if (typeof roomId === "string") rooms.add(roomId);
721
- }
722
- }
831
+ return direct ? toDirectMapping(direct.content) : void 0;
832
+ }
833
+ function directRoomIds(accountDataEvents) {
834
+ return directMappingRooms(directMappingFromEvents(accountDataEvents) ?? {});
835
+ }
836
+ function withDirectRoom(mapping, peer, roomId) {
837
+ const existing = mapping[peer] ?? [];
838
+ if (existing.includes(roomId)) return void 0;
839
+ return { ...mapping, [peer]: [...existing, roomId] };
840
+ }
841
+ function directInviteFrom(room, me) {
842
+ for (const event of room.invite_state?.events ?? []) {
843
+ if (event.type !== "m.room.member" || event.state_key !== me.userId) continue;
844
+ const content = event.content ?? {};
845
+ if (content.membership !== "invite") continue;
846
+ if (content.is_direct === true) return event.sender;
723
847
  }
724
- return rooms;
848
+ return void 0;
725
849
  }
726
850
  function toChannelMessage(roomId, event, me, directRooms, sentEventIds) {
727
851
  if (event.type !== "m.room.message") return void 0;
728
852
  const content = event.content;
729
853
  if (!content.msgtype) return void 0;
854
+ if (content["m.relates_to"]?.rel_type === "m.replace") return void 0;
730
855
  return {
731
856
  chatId: roomId,
732
857
  senderId: event.sender,
@@ -755,19 +880,44 @@ function createMatrixSyncHandler(deps) {
755
880
  const { api, me, pipeline, allowedChats, sentEventIds, logger, label } = deps;
756
881
  const autoJoin = deps.autoJoin ?? true;
757
882
  const loggedUnallowedChats = /* @__PURE__ */ new Set();
758
- let directRooms = new Set(deps.initialDirectRooms ?? []);
883
+ const loggedDeclinedInvites = /* @__PURE__ */ new Set();
884
+ let directMapping = { ...deps.initialDirect ?? {} };
885
+ let directRooms = directMappingRooms(directMapping);
886
+ async function rememberDirectRoom(peer, roomId) {
887
+ directRooms.add(roomId);
888
+ const merged = withDirectRoom(directMapping, peer, roomId);
889
+ if (!merged) return;
890
+ directMapping = merged;
891
+ try {
892
+ await api.setAccountData(me.userId, "m.direct", directMapping);
893
+ } catch (error) {
894
+ logger.warn(`${label}: could not record ${roomId} in m.direct:`, error);
895
+ }
896
+ }
759
897
  return async function handleSync(response) {
760
- if (response.account_data?.events?.some((event) => event.type === "m.direct")) {
761
- directRooms = directRoomIds(response.account_data.events);
898
+ const syncedDirect = directMappingFromEvents(response.account_data?.events ?? []);
899
+ if (syncedDirect) {
900
+ directMapping = syncedDirect;
901
+ directRooms = directMappingRooms(directMapping);
762
902
  }
763
903
  if (autoJoin) {
764
- for (const roomId of Object.keys(response.rooms?.invite ?? {})) {
904
+ for (const [roomId, room] of Object.entries(response.rooms?.invite ?? {})) {
905
+ if (allowedChats.length > 0 && !allowedChats.includes(roomId)) {
906
+ if (!loggedDeclinedInvites.has(roomId)) {
907
+ loggedDeclinedInvites.add(roomId);
908
+ logger.warn(`${label}: left invite to ${roomId} pending - not in allowedChats`);
909
+ }
910
+ continue;
911
+ }
912
+ const directInviter = directInviteFrom(room, me);
765
913
  try {
766
914
  await api.joinRoom(roomId);
767
915
  logger.info(`${label}: joined ${roomId} (accepted invite)`);
768
916
  } catch (error) {
769
917
  logger.warn(`${label}: failed to join ${roomId}:`, error);
918
+ continue;
770
919
  }
920
+ if (directInviter) await rememberDirectRoom(directInviter, roomId);
771
921
  }
772
922
  }
773
923
  for (const [roomId, room] of Object.entries(response.rooms?.join ?? {})) {
@@ -816,11 +966,16 @@ function createMatrixSender(api, sentEventIds) {
816
966
  };
817
967
  }
818
968
 
969
+ // src/channels/backoff.ts
970
+ function exponentialBackoffMs(baseMs, maxMs, n) {
971
+ return Math.min(baseMs * 2 ** Math.max(0, Math.min(n, 20)), maxMs);
972
+ }
973
+
819
974
  // src/channels/matrix/sync.ts
820
975
  var BASE_DELAY_MS = 1e3;
821
976
  var MAX_DELAY_MS = 6e4;
822
977
  function syncBackoffMs(failures) {
823
- return Math.min(BASE_DELAY_MS * 2 ** Math.min(failures, 20), MAX_DELAY_MS);
978
+ return exponentialBackoffMs(BASE_DELAY_MS, MAX_DELAY_MS, failures);
824
979
  }
825
980
  function retryDelayMs(error, failures) {
826
981
  const retryAfterMs = error instanceof MatrixApiError ? error.retryAfterMs : void 0;
@@ -844,9 +999,9 @@ async function runMatrixSyncLoop(deps) {
844
999
  continue;
845
1000
  }
846
1001
  failures = 0;
1002
+ if (deps.isStopped()) break;
847
1003
  since = response.next_batch;
848
1004
  deps.onSince?.(since);
849
- if (deps.isStopped()) break;
850
1005
  try {
851
1006
  await deps.handleSync(response);
852
1007
  } catch (error) {
@@ -858,12 +1013,6 @@ async function runMatrixSyncLoop(deps) {
858
1013
 
859
1014
  // src/channels/matrix/channel.ts
860
1015
  var DEFAULT_SYNC_TIMEOUT_MS = 3e4;
861
- function defaultSleep(ms) {
862
- return new Promise((resolve) => {
863
- const timer = setTimeout(resolve, ms);
864
- if (typeof timer === "object" && "unref" in timer) timer.unref();
865
- });
866
- }
867
1016
  function createMatrixChannel(config) {
868
1017
  const channelName = config.name ?? "matrix";
869
1018
  const allowedChats = config.allowedChats ?? [];
@@ -871,6 +1020,7 @@ function createMatrixChannel(config) {
871
1020
  const syncTimeoutMs = config.syncTimeoutMs ?? DEFAULT_SYNC_TIMEOUT_MS;
872
1021
  let stopped = false;
873
1022
  let registered = false;
1023
+ let abort;
874
1024
  let senders;
875
1025
  const sentEventIds = /* @__PURE__ */ new Set();
876
1026
  return {
@@ -880,14 +1030,15 @@ function createMatrixChannel(config) {
880
1030
  const api = config.api ?? createMatrixApi({
881
1031
  homeserverUrl: config.homeserverUrl,
882
1032
  accessToken: config.accessToken,
883
- fetch: config.fetch
1033
+ fetch: config.fetch,
1034
+ maxMediaBytes: config.maxMediaBytes
884
1035
  });
885
1036
  const me = await api.whoami();
886
1037
  const label = `Matrix[${me.userId}]`;
887
- let initialDirectRooms;
1038
+ let initialDirect;
888
1039
  try {
889
1040
  const direct = await api.getAccountData(me.userId, "m.direct");
890
- if (direct) initialDirectRooms = directRoomIds([{ type: "m.direct", content: direct }]);
1041
+ if (direct) initialDirect = toDirectMapping(direct);
891
1042
  } catch (error) {
892
1043
  log.warn(`${label}: could not load m.direct account data at start:`, error);
893
1044
  }
@@ -898,11 +1049,7 @@ function createMatrixChannel(config) {
898
1049
  { client: deps.client, store: deps.store, prompts: deps.prompts },
899
1050
  {
900
1051
  channel: channelName,
901
- answerFn: config.answerFn ?? deps.config.answerFn,
902
- bucketsFor: config.bucketsFor ?? deps.config.bucketsFor,
903
- rewriteQuery: config.rewriteQuery ?? deps.config.rewriteQuery,
904
- rerankContext: config.rerankContext ?? deps.config.rerankContext,
905
- transformReply: config.transformReply ?? deps.config.transformReply,
1052
+ ...resolveBrainHooks(config, deps.config),
906
1053
  model: config.model,
907
1054
  channelHint: config.channelHint ?? "Channel: Matrix.",
908
1055
  personaResolver: config.personaResolver,
@@ -925,12 +1072,14 @@ function createMatrixChannel(config) {
925
1072
  allowedChats,
926
1073
  sentEventIds,
927
1074
  autoJoin: config.autoJoin,
928
- initialDirectRooms,
1075
+ initialDirect,
929
1076
  logger: log,
930
1077
  label
931
1078
  });
1079
+ abort = new AbortController();
1080
+ const signal = abort.signal;
932
1081
  void runMatrixSyncLoop({
933
- sync: (since) => api.sync({ since, timeoutMs: syncTimeoutMs }),
1082
+ sync: (since) => api.sync({ since, timeoutMs: syncTimeoutMs, signal }),
934
1083
  handleSync,
935
1084
  isStopped: () => stopped,
936
1085
  sleep,
@@ -943,6 +1092,7 @@ function createMatrixChannel(config) {
943
1092
  },
944
1093
  stop() {
945
1094
  stopped = true;
1095
+ abort?.abort();
946
1096
  if (registered) {
947
1097
  senders?.unregister(channelName);
948
1098
  registered = false;
@@ -952,12 +1102,16 @@ function createMatrixChannel(config) {
952
1102
  }
953
1103
  // Annotate the CommonJS export names for ESM import in node:
954
1104
  0 && (module.exports = {
1105
+ DEFAULT_MAX_MEDIA_BYTES,
955
1106
  MAX_TRACKED_SENT_EVENTS,
956
1107
  MatrixApiError,
957
1108
  createMatrixApi,
958
1109
  createMatrixChannel,
959
1110
  createMatrixSender,
960
1111
  createMatrixSyncHandler,
1112
+ directInviteFrom,
1113
+ directMappingFromEvents,
1114
+ directMappingRooms,
961
1115
  directRoomIds,
962
1116
  isReplyToBot,
963
1117
  matrixSenderKey,
@@ -968,5 +1122,7 @@ function createMatrixChannel(config) {
968
1122
  retryDelayMs,
969
1123
  runMatrixSyncLoop,
970
1124
  syncBackoffMs,
971
- toChannelMessage
1125
+ toChannelMessage,
1126
+ toDirectMapping,
1127
+ withDirectRoom
972
1128
  });