@diegoaltoworks/chatter 0.60.0 → 0.62.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 +56 -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 +152 -0
  15. package/dist/channels/matrix/api.d.ts.map +1 -0
  16. package/dist/channels/matrix/channel.d.ts +46 -0
  17. package/dist/channels/matrix/channel.d.ts.map +1 -0
  18. package/dist/channels/matrix/handler.d.ts +58 -0
  19. package/dist/channels/matrix/handler.d.ts.map +1 -0
  20. package/dist/channels/matrix/index.d.ts +37 -0
  21. package/dist/channels/matrix/index.d.ts.map +1 -0
  22. package/dist/channels/matrix/index.js +1128 -0
  23. package/dist/channels/matrix/index.mjs +1079 -0
  24. package/dist/channels/matrix/sync.d.ts +53 -0
  25. package/dist/channels/matrix/sync.d.ts.map +1 -0
  26. package/dist/channels/matrix/updates.d.ts +104 -0
  27. package/dist/channels/matrix/updates.d.ts.map +1 -0
  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 +25 -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 +123 -41
  49. package/dist/channels/whatsapp/index.mjs +120 -41
  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 +11 -2
@@ -27,6 +27,7 @@ __export(index_exports, {
27
27
  isBlockedByAllowlist: () => isBlockedByAllowlist,
28
28
  isEffectivelyFromSelf: () => isEffectivelyFromSelf,
29
29
  isFromAnySession: () => isFromAnySession,
30
+ resolveBrainHooks: () => resolveBrainHooks,
30
31
  underReplyRateLimit: () => underReplyRateLimit
31
32
  });
32
33
  module.exports = __toCommonJS(index_exports);
@@ -163,7 +164,8 @@ async function answerOnce({
163
164
  async function applyTransformReply(transformReply, input, logger) {
164
165
  if (!transformReply) return input.text;
165
166
  try {
166
- return await transformReply(input);
167
+ const result = await transformReply(input);
168
+ return typeof result === "string" ? result : null;
167
169
  } catch (error) {
168
170
  logger?.error(
169
171
  `transformReply threw for channel "${input.channel}"; sending the original reply`,
@@ -225,6 +227,7 @@ async function prepareChat({
225
227
  sender,
226
228
  rewriteQuery,
227
229
  rerankContext,
230
+ fallbackFn,
228
231
  logger
229
232
  }) {
230
233
  const lastUserMsg = lastUserMessage(messages);
@@ -241,21 +244,39 @@ async function prepareChat({
241
244
  logger?.error("rewriteQuery threw; falling back to the original query", error);
242
245
  }
243
246
  }
244
- let ctx = await store.query(query, topK, buckets ?? defaultBuckets(mode));
247
+ const resolvedBuckets = buckets ?? defaultBuckets(mode);
248
+ const retrievedChunks = await store.query(query, topK, resolvedBuckets);
249
+ let ctx = retrievedChunks;
245
250
  if (rerankContext) {
246
251
  try {
247
- const reranked = await rerankContext({ query, chunks: ctx });
252
+ const reranked = await rerankContext({ query, chunks: [...ctx] });
248
253
  if (Array.isArray(reranked) && reranked.every((c) => typeof c === "string")) ctx = reranked;
249
254
  } catch (error) {
250
255
  logger?.error("rerankContext threw; falling back to the original chunks", error);
251
256
  }
252
257
  }
258
+ let fallback;
259
+ if (ctx.length === 0 && fallbackFn) {
260
+ try {
261
+ const guidance = await fallbackFn({
262
+ query,
263
+ mode,
264
+ buckets: resolvedBuckets,
265
+ sender,
266
+ retrievedChunks
267
+ });
268
+ if (typeof guidance === "string" && guidance.trim()) fallback = guidance.trim();
269
+ } catch (error) {
270
+ logger?.error("fallbackFn threw; proceeding without fallback guidance", error);
271
+ }
272
+ }
253
273
  const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
254
274
  const hint = channelHint?.trim();
255
275
  const system = [
256
276
  prompts.baseSystemRules,
257
277
  persona,
258
278
  ...hint ? [hint] : [],
279
+ ...fallback ? [fallback] : [],
259
280
  `${label}:
260
281
  ${ctx.join("\n\n")}`
261
282
  ].join("\n\n");
@@ -263,7 +284,7 @@ ${ctx.join("\n\n")}`
263
284
  }
264
285
 
265
286
  // src/history/compaction.ts
266
- var DEFAULT_TIMEOUT_MS = 8e3;
287
+ var COMPACTION_TIMEOUT_MS = 8e3;
267
288
  var LOAD_ALL = Number.MAX_SAFE_INTEGER;
268
289
  var SUMMARY_PREFIX = "Summary of earlier conversation: ";
269
290
  function defaultSummarize(client, model) {
@@ -291,7 +312,7 @@ function createHistoryCompactor(deps, options) {
291
312
  throw new Error(`Invalid history compaction keep: ${keep}`);
292
313
  }
293
314
  const summarize = options.summarize ?? defaultSummarize(deps.client, options.model);
294
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
315
+ const timeoutMs = options.timeoutMs ?? COMPACTION_TIMEOUT_MS;
295
316
  return {
296
317
  async maybeCompact(store, conversationId) {
297
318
  const turns = await store.load(conversationId, LOAD_ALL);
@@ -332,6 +353,16 @@ function createHistoryCompactor(deps, options) {
332
353
  }
333
354
 
334
355
  // src/channels/pipeline.ts
356
+ function resolveBrainHooks(config, fallback) {
357
+ return {
358
+ answerFn: config.answerFn ?? fallback?.answerFn,
359
+ bucketsFor: config.bucketsFor ?? fallback?.bucketsFor,
360
+ rewriteQuery: config.rewriteQuery ?? fallback?.rewriteQuery,
361
+ rerankContext: config.rerankContext ?? fallback?.rerankContext,
362
+ fallbackFn: config.fallbackFn ?? fallback?.fallbackFn,
363
+ transformReply: config.transformReply ?? fallback?.transformReply
364
+ };
365
+ }
335
366
  var DEFAULT_HISTORY_LIMIT = 20;
336
367
  var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
337
368
  var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
@@ -416,6 +447,7 @@ function createInboundPipeline(deps, config) {
416
447
  sender,
417
448
  rewriteQuery: config.rewriteQuery,
418
449
  rerankContext: config.rerankContext,
450
+ fallbackFn: config.fallbackFn,
419
451
  logger: config.logger
420
452
  });
421
453
  const { content: produced } = await answerOnce({
@@ -539,5 +571,6 @@ function createSenderRegistry(logger = createConsoleLogger()) {
539
571
  isBlockedByAllowlist,
540
572
  isEffectivelyFromSelf,
541
573
  isFromAnySession,
574
+ resolveBrainHooks,
542
575
  underReplyRateLimit
543
576
  });
@@ -130,7 +130,8 @@ async function answerOnce({
130
130
  async function applyTransformReply(transformReply, input, logger) {
131
131
  if (!transformReply) return input.text;
132
132
  try {
133
- return await transformReply(input);
133
+ const result = await transformReply(input);
134
+ return typeof result === "string" ? result : null;
134
135
  } catch (error) {
135
136
  logger?.error(
136
137
  `transformReply threw for channel "${input.channel}"; sending the original reply`,
@@ -192,6 +193,7 @@ async function prepareChat({
192
193
  sender,
193
194
  rewriteQuery,
194
195
  rerankContext,
196
+ fallbackFn,
195
197
  logger
196
198
  }) {
197
199
  const lastUserMsg = lastUserMessage(messages);
@@ -208,21 +210,39 @@ async function prepareChat({
208
210
  logger?.error("rewriteQuery threw; falling back to the original query", error);
209
211
  }
210
212
  }
211
- let ctx = await store.query(query, topK, buckets ?? defaultBuckets(mode));
213
+ const resolvedBuckets = buckets ?? defaultBuckets(mode);
214
+ const retrievedChunks = await store.query(query, topK, resolvedBuckets);
215
+ let ctx = retrievedChunks;
212
216
  if (rerankContext) {
213
217
  try {
214
- const reranked = await rerankContext({ query, chunks: ctx });
218
+ const reranked = await rerankContext({ query, chunks: [...ctx] });
215
219
  if (Array.isArray(reranked) && reranked.every((c) => typeof c === "string")) ctx = reranked;
216
220
  } catch (error) {
217
221
  logger?.error("rerankContext threw; falling back to the original chunks", error);
218
222
  }
219
223
  }
224
+ let fallback;
225
+ if (ctx.length === 0 && fallbackFn) {
226
+ try {
227
+ const guidance = await fallbackFn({
228
+ query,
229
+ mode,
230
+ buckets: resolvedBuckets,
231
+ sender,
232
+ retrievedChunks
233
+ });
234
+ if (typeof guidance === "string" && guidance.trim()) fallback = guidance.trim();
235
+ } catch (error) {
236
+ logger?.error("fallbackFn threw; proceeding without fallback guidance", error);
237
+ }
238
+ }
220
239
  const persona = personaLayer?.trim() || (mode === "private" ? prompts.privatePersona : prompts.publicPersona);
221
240
  const hint = channelHint?.trim();
222
241
  const system = [
223
242
  prompts.baseSystemRules,
224
243
  persona,
225
244
  ...hint ? [hint] : [],
245
+ ...fallback ? [fallback] : [],
226
246
  `${label}:
227
247
  ${ctx.join("\n\n")}`
228
248
  ].join("\n\n");
@@ -230,7 +250,7 @@ ${ctx.join("\n\n")}`
230
250
  }
231
251
 
232
252
  // src/history/compaction.ts
233
- var DEFAULT_TIMEOUT_MS = 8e3;
253
+ var COMPACTION_TIMEOUT_MS = 8e3;
234
254
  var LOAD_ALL = Number.MAX_SAFE_INTEGER;
235
255
  var SUMMARY_PREFIX = "Summary of earlier conversation: ";
236
256
  function defaultSummarize(client, model) {
@@ -258,7 +278,7 @@ function createHistoryCompactor(deps, options) {
258
278
  throw new Error(`Invalid history compaction keep: ${keep}`);
259
279
  }
260
280
  const summarize = options.summarize ?? defaultSummarize(deps.client, options.model);
261
- const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
281
+ const timeoutMs = options.timeoutMs ?? COMPACTION_TIMEOUT_MS;
262
282
  return {
263
283
  async maybeCompact(store, conversationId) {
264
284
  const turns = await store.load(conversationId, LOAD_ALL);
@@ -299,6 +319,16 @@ function createHistoryCompactor(deps, options) {
299
319
  }
300
320
 
301
321
  // src/channels/pipeline.ts
322
+ function resolveBrainHooks(config, fallback) {
323
+ return {
324
+ answerFn: config.answerFn ?? fallback?.answerFn,
325
+ bucketsFor: config.bucketsFor ?? fallback?.bucketsFor,
326
+ rewriteQuery: config.rewriteQuery ?? fallback?.rewriteQuery,
327
+ rerankContext: config.rerankContext ?? fallback?.rerankContext,
328
+ fallbackFn: config.fallbackFn ?? fallback?.fallbackFn,
329
+ transformReply: config.transformReply ?? fallback?.transformReply
330
+ };
331
+ }
302
332
  var DEFAULT_HISTORY_LIMIT = 20;
303
333
  var DEFAULT_GROUP_RATE_LIMIT = { max: 30, windowMs: 60 * 60 * 1e3 };
304
334
  var DEFAULT_DM_RATE_LIMIT = { max: 20, windowMs: 60 * 60 * 1e3 };
@@ -383,6 +413,7 @@ function createInboundPipeline(deps, config) {
383
413
  sender,
384
414
  rewriteQuery: config.rewriteQuery,
385
415
  rerankContext: config.rerankContext,
416
+ fallbackFn: config.fallbackFn,
386
417
  logger: config.logger
387
418
  });
388
419
  const { content: produced } = await answerOnce({
@@ -505,5 +536,6 @@ export {
505
536
  isBlockedByAllowlist,
506
537
  isEffectivelyFromSelf,
507
538
  isFromAnySession,
539
+ resolveBrainHooks,
508
540
  underReplyRateLimit
509
541
  };
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Matrix Client-Server API client — the whole transport layer of the Matrix
3
+ * channel, over plain `fetch`. No SDK, no optional peer dependency: the
4
+ * client-server API is JSON over HTTPS (plus one raw-bytes upload endpoint),
5
+ * so `./matrix` costs a consumer nothing beyond the package itself.
6
+ *
7
+ * Everything here is about the wire: envelopes, error mapping, sync polling,
8
+ * sending, media upload. Interpretation of a room event (mentions, DMs,
9
+ * reply-to) lives in `./updates` and `./channel`.
10
+ *
11
+ * The access token is a credential carried in an `Authorization: Bearer`
12
+ * header, never a query string, so it never lands in a URL an error message
13
+ * might echo back — but a homeserver's own error body could still quote it
14
+ * back verbatim (a malformed-token 401, say), so it is redacted the same
15
+ * defensive way the Telegram client redacts its token — see
16
+ * {@link redactToken}.
17
+ */
18
+ export interface MatrixEvent {
19
+ type: string;
20
+ event_id: string;
21
+ sender: string;
22
+ origin_server_ts: number;
23
+ content: Record<string, unknown>;
24
+ unsigned?: Record<string, unknown>;
25
+ }
26
+ export interface MatrixRoomTimeline {
27
+ events: MatrixEvent[];
28
+ /**
29
+ * True when the server omitted earlier events for size — a bot only ever
30
+ * reads forward from its own `since` token, so a limited timeline just
31
+ * means "more happened than fit in this batch", not a gap it needs to
32
+ * backfill; every event actually included here is still handled normally.
33
+ */
34
+ limited?: boolean;
35
+ prev_batch?: string;
36
+ }
37
+ export interface MatrixJoinedRoom {
38
+ timeline: MatrixRoomTimeline;
39
+ }
40
+ /**
41
+ * A state event as it arrives in an invite's `invite_state`: the homeserver
42
+ * strips it down to type/state_key/sender/content for a room the bot has not
43
+ * joined, so there is no `event_id` or `origin_server_ts` here the way there
44
+ * is on a timeline event.
45
+ */
46
+ export interface MatrixStrippedStateEvent {
47
+ type: string;
48
+ state_key?: string;
49
+ sender?: string;
50
+ content?: Record<string, unknown>;
51
+ }
52
+ export interface MatrixInvitedRoom {
53
+ invite_state?: {
54
+ events: MatrixStrippedStateEvent[];
55
+ };
56
+ }
57
+ export interface MatrixAccountDataEvent {
58
+ type: string;
59
+ content: Record<string, unknown>;
60
+ }
61
+ export interface MatrixSyncResponse {
62
+ next_batch: string;
63
+ rooms?: {
64
+ join?: Record<string, MatrixJoinedRoom>;
65
+ invite?: Record<string, MatrixInvitedRoom>;
66
+ };
67
+ account_data?: {
68
+ events?: MatrixAccountDataEvent[];
69
+ };
70
+ }
71
+ /** A client-server API call that returned a non-2xx, or never completed. `retryAfterMs` carries `M_LIMIT_EXCEEDED`'s own wait instruction when the homeserver sent one. */
72
+ export declare class MatrixApiError extends Error {
73
+ readonly errcode?: string;
74
+ readonly status?: number;
75
+ readonly retryAfterMs?: number;
76
+ constructor(message: string, options?: {
77
+ errcode?: string;
78
+ status?: number;
79
+ retryAfterMs?: number;
80
+ });
81
+ }
82
+ /**
83
+ * Replaces every occurrence of `token` with `***`. An empty token is left
84
+ * alone: replacing "" would corrupt the message rather than protect
85
+ * anything.
86
+ */
87
+ export declare function redactToken(text: string, token: string): string;
88
+ export type MatrixMediaKind = "image" | "file" | "video" | "audio";
89
+ /** What `ChannelSender.sendMedia` accepts for this channel. A bare string is shorthand for an already-uploaded `mxc://` URI or an https URL to fetch and re-upload. */
90
+ export interface MatrixMediaPayload {
91
+ /** @default "image" */
92
+ kind?: MatrixMediaKind;
93
+ /** An `mxc://` content URI (sent as-is) or an https URL (fetched under {@link MatrixApiConfig.maxMediaBytes}, then uploaded). Any other scheme is rejected. */
94
+ url: string;
95
+ caption?: string;
96
+ /** @default derived from the URL's last path segment, or the media kind */
97
+ filename?: string;
98
+ }
99
+ /**
100
+ * Default ceiling on a fetched media source. `sendMedia` is reachable from a
101
+ * scheduler, a flow or an `answerFn` tool call, so its URL is not necessarily
102
+ * one an operator chose: an unbounded fetch would let whatever produced that
103
+ * URL decide how much memory this process allocates.
104
+ */
105
+ export declare const DEFAULT_MAX_MEDIA_BYTES: number;
106
+ export interface MatrixApiConfig {
107
+ /** Homeserver client-server API origin, e.g. `https://matrix.example.org`. */
108
+ homeserverUrl: string;
109
+ /** A logged-in bot user's access token. A credential — pass it from the environment, never commit it. */
110
+ accessToken: string;
111
+ /** Overridable for tests and for hosts routing through a proxy; defaults to `globalThis.fetch`. */
112
+ fetch?: typeof fetch;
113
+ /** Cap on the bytes `sendMedia` will pull from a remote https URL before uploading. @default 25 MiB */
114
+ maxMediaBytes?: number;
115
+ }
116
+ export interface MatrixApi {
117
+ /** Raw call, for an endpoint this interface does not wrap. Rejects with {@link MatrixApiError} on any non-2xx response. */
118
+ call<T>(method: string, path: string, options?: {
119
+ body?: unknown;
120
+ query?: Record<string, string | number | undefined>;
121
+ signal?: AbortSignal;
122
+ }): Promise<T>;
123
+ whoami(): Promise<{
124
+ userId: string;
125
+ }>;
126
+ sync(options: {
127
+ since?: string;
128
+ timeoutMs: number;
129
+ signal?: AbortSignal;
130
+ }): Promise<MatrixSyncResponse>;
131
+ /** A user's global account data of `type` (e.g. `m.direct`), or `undefined` if it was never set (`M_NOT_FOUND`). */
132
+ getAccountData<T>(userId: string, type: string): Promise<T | undefined>;
133
+ /** Replaces a user's global account data of `type`. Whole-document semantics: the homeserver stores exactly what is sent, so merge before calling. */
134
+ setAccountData(userId: string, type: string, content: Record<string, unknown>): Promise<void>;
135
+ /** Sends an `m.room.message` (or any event type, via `eventType`) and returns its event id. */
136
+ sendEvent(roomId: string, content: Record<string, unknown>, options?: {
137
+ eventType?: string;
138
+ }): Promise<{
139
+ eventId: string;
140
+ }>;
141
+ /** Uploads raw bytes to the media repository and returns its `mxc://` content URI. */
142
+ uploadMedia(bytes: Uint8Array, contentType: string, filename?: string): Promise<{
143
+ contentUri: string;
144
+ }>;
145
+ /** Fetches `url`, uploads the bytes, and sends the resulting `m.room.message` — the whole `MatrixMediaPayload` -> delivered-event path. */
146
+ sendMedia(roomId: string, payload: unknown): Promise<{
147
+ eventId: string;
148
+ }>;
149
+ joinRoom(roomIdOrAlias: string): Promise<void>;
150
+ }
151
+ export declare function createMatrixApi(config: MatrixApiConfig): MatrixApi;
152
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/channels/matrix/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Matrix client-server transport: a {@link Channel} that long-polls
3
+ * `/sync`, maps each room event into a `ChannelMessage`, and answers
4
+ * through the shared `createInboundPipeline` — the same gates, persona,
5
+ * buckets, history and `answerFn` seams every other channel uses.
6
+ *
7
+ * Deliberately the plain client-server HTTP API over `fetch`, not an SDK:
8
+ * no optional peer dependency, same as `../telegram`. That also means
9
+ * **no end-to-end encryption support** — an encrypted room's events arrive
10
+ * as opaque `m.room.encrypted` ciphertext this channel cannot decrypt, so
11
+ * it silently cannot see anything sent there. Use this channel only in
12
+ * unencrypted rooms; see docs/matrix.md for the full explanation and why
13
+ * that rules out most default-E2EE personal homeservers for DMs.
14
+ */
15
+ import type { Channel } from "../index";
16
+ import { type PollingChannelConfig } from "../polling";
17
+ import { type MatrixApi } from "./api";
18
+ export interface MatrixChannelConfig extends PollingChannelConfig {
19
+ /** Homeserver client-server API origin, e.g. `https://matrix.example.org`. */
20
+ homeserverUrl: string;
21
+ /** A logged-in bot user's access token. A credential — pass it from the environment, never commit it. */
22
+ accessToken: string;
23
+ /** Channel and sender-registry name. Override to run more than one bot in one process. @default "matrix" */
24
+ name?: string;
25
+ /** Extra system-prompt section describing the delivery channel; passed through to `prepareChat`. @default "Channel: Matrix." */
26
+ channelHint?: string;
27
+ /** A throw/rejection is treated as "no persona" for that turn. `sender` is the namespaced `mx:<user id>` key. */
28
+ personaResolver?: (ctx: {
29
+ sender: string;
30
+ text: string;
31
+ }) => string | undefined | Promise<string | undefined>;
32
+ /** Auto-accept room invites, so the bot can actually receive messages in a room a user just invited it to. @default true */
33
+ autoJoin?: boolean;
34
+ /** @default 30000 */
35
+ syncTimeoutMs?: number;
36
+ /** Resume point for `/sync`. Omitted = an initial full sync (see docs/matrix.md on `since`-token persistence). */
37
+ initialSince?: string;
38
+ /** Called with each acknowledged `since` token, for a host that wants to persist it across restarts. */
39
+ onSince?: (since: string) => void;
40
+ /** Cap on the bytes a `sendMedia` https URL may pull before upload. @default 25 MiB */
41
+ maxMediaBytes?: number;
42
+ /** Overridable for tests, which fake the client-server API instead of calling it; defaults to a `fetch` client over {@link MatrixChannelConfig.homeserverUrl}/{@link MatrixChannelConfig.accessToken}. */
43
+ api?: MatrixApi;
44
+ }
45
+ export declare function createMatrixChannel(config: MatrixChannelConfig): Channel;
46
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../../src/channels/matrix/channel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,OAAO,EAAgB,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAmB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAQxD,MAAM,WAAW,mBAAoB,SAAQ,oBAAoB;IAC/D,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,yGAAyG;IACzG,WAAW,EAAE,MAAM,CAAC;IACpB,4GAA4G;IAC5G,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gIAAgI;IAChI,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iHAAiH;IACjH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE;QACtB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;KACd,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACvD,4HAA4H;IAC5H,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kHAAkH;IAClH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wGAAwG;IACxG,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0MAA0M;IAC1M,GAAG,CAAC,EAAE,SAAS,CAAC;CACjB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CA8HxE"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The per-sync-batch handling logic for the Matrix channel: room-invite
3
+ * auto-join, `m.direct` account-data tracking, event -> `ChannelMessage`
4
+ * mapping, and pipeline dispatch — the Matrix analogue of
5
+ * `../telegram/handler.ts`, adapted to a `/sync` response covering several
6
+ * rooms and events at once instead of one Telegram update.
7
+ */
8
+ import type { Logger } from "../../core/logger";
9
+ import type { InboundPipeline } from "../pipeline";
10
+ import type { ChannelSender } from "../senders";
11
+ import type { MatrixApi, MatrixSyncResponse } from "./api";
12
+ import { type MatrixDirectMapping, type MatrixIdentity } from "./updates";
13
+ export interface MatrixSyncHandlerDeps {
14
+ api: MatrixApi;
15
+ me: MatrixIdentity;
16
+ pipeline: InboundPipeline;
17
+ /** Group rooms eligible for a reply. Empty = every room. */
18
+ allowedChats: string[];
19
+ /**
20
+ * This session's own sent event ids, for reply-to-bot detection (see
21
+ * `./updates`'s `isReplyToBot`) — shared with, and appended to by, the
22
+ * `ChannelSender` this channel registers, so a reply sent outside a
23
+ * pipeline turn (a scheduler, a flow) is still recognised.
24
+ */
25
+ sentEventIds: Set<string>;
26
+ /**
27
+ * Auto-accept room invites so the bot can actually receive messages there.
28
+ * Gated by {@link MatrixSyncHandlerDeps.allowedChats} when that is set:
29
+ * an invite is an unauthenticated request from any user on any federated
30
+ * homeserver, so a configured allowlist governs which rooms the bot will
31
+ * join, not just which ones it will answer in.
32
+ * @default true
33
+ */
34
+ autoJoin?: boolean;
35
+ /**
36
+ * The bot's own `m.direct` mapping as of `start()` (see `./channel`'s
37
+ * upfront `getAccountData("m.direct")` fetch), seeded here because an
38
+ * initial `/sync` response's `account_data` isn't guaranteed to repeat
39
+ * `m.direct` and a resumed sync (with `initialSince`) never carries it
40
+ * again unless it changes, so without this a restart would treat every
41
+ * known DM as an addressing-required group until it next changes.
42
+ */
43
+ initialDirect?: MatrixDirectMapping;
44
+ logger: Logger;
45
+ /** Log-line prefix, e.g. `Matrix[@bot:example.org]`. */
46
+ label: string;
47
+ }
48
+ /**
49
+ * One `/sync` response -> zero or more pipeline turns, one per
50
+ * `m.room.message` event across every joined room in the batch. Builds its
51
+ * own allowlist-log dedup set, so each transport instance gets its own
52
+ * "skipped room" throttling rather than sharing (or fighting over) state
53
+ * with another.
54
+ */
55
+ export declare function createMatrixSyncHandler(deps: MatrixSyncHandlerDeps): (response: MatrixSyncResponse) => Promise<void>;
56
+ /** The `ChannelSender` every Matrix transport registers — text, media and reactions over the same `MatrixApi`. */
57
+ export declare function createMatrixSender(api: MatrixApi, sentEventIds: Set<string>): ChannelSender;
58
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/channels/matrix/handler.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,aAAa,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,OAAO,CAAC;AAC3D,OAAO,EAIL,KAAK,mBAAmB,EACxB,KAAK,cAAc,EAKpB,MAAM,WAAW,CAAC;AAmBnB,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,SAAS,CAAC;IACf,EAAE,EAAE,cAAc,CAAC;IACnB,QAAQ,EAAE,eAAe,CAAC;IAC1B,4DAA4D;IAC5D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB;;;;;OAKG;IACH,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC1B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,qBAAqB,GAC1B,CAAC,QAAQ,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAsGjD;AAED,kHAAkH;AAClH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,aAAa,CAkB3F"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Matrix client-server transport — a {@link Channel} with NO optional peer
3
+ * dependency: the client-server API is JSON over HTTPS (plus one raw-bytes
4
+ * upload endpoint), reached with plain `fetch`, so this subpath costs a
5
+ * consumer nothing beyond the package itself.
6
+ *
7
+ * ```ts
8
+ * import { createMatrixChannel } from "@diegoaltoworks/chatter/matrix";
9
+ *
10
+ * await createServer({
11
+ * ...,
12
+ * channels: [
13
+ * createMatrixChannel({
14
+ * homeserverUrl: process.env.MATRIX_HOMESERVER_URL as string,
15
+ * accessToken: process.env.MATRIX_ACCESS_TOKEN as string,
16
+ * }),
17
+ * ],
18
+ * });
19
+ * ```
20
+ *
21
+ * **End-to-end encryption is not supported.** This channel only sees
22
+ * unencrypted rooms — see docs/matrix.md for the full explanation before
23
+ * relying on this for DMs, which most clients encrypt by default.
24
+ *
25
+ * Everything past turning a room event into a `ChannelMessage` runs through
26
+ * `./channels`' `createInboundPipeline`, shared with every other channel —
27
+ * see docs/matrix.md for configuration and docs/build-a-channel.md for the
28
+ * SPI this implements.
29
+ *
30
+ * @packageDocumentation
31
+ */
32
+ export { createMatrixApi, DEFAULT_MAX_MEDIA_BYTES, type MatrixAccountDataEvent, type MatrixApi, type MatrixApiConfig, MatrixApiError, type MatrixEvent, type MatrixInvitedRoom, type MatrixJoinedRoom, type MatrixMediaKind, type MatrixMediaPayload, type MatrixRoomTimeline, type MatrixStrippedStateEvent, type MatrixSyncResponse, redactToken, } from "./api";
33
+ export { createMatrixChannel, type MatrixChannelConfig } from "./channel";
34
+ export { createMatrixSender, createMatrixSyncHandler, type MatrixSyncHandlerDeps, } from "./handler";
35
+ export { retryDelayMs, runMatrixSyncLoop, syncBackoffMs } from "./sync";
36
+ export { directInviteFrom, directMappingFromEvents, directMappingRooms, directRoomIds, isReplyToBot, MAX_TRACKED_SENT_EVENTS, type MatrixDirectMapping, type MatrixIdentity, matrixSenderKey, mentionsBot, messageText, recordSentEventId, toChannelMessage, toDirectMapping, withDirectRoom, } from "./updates";
37
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/channels/matrix/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,SAAS,EACd,KAAK,eAAe,EACpB,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,WAAW,GACZ,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,mBAAmB,EAAE,KAAK,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AACxE,OAAO,EACL,gBAAgB,EAChB,uBAAuB,EACvB,kBAAkB,EAClB,aAAa,EACb,YAAY,EACZ,uBAAuB,EACvB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,eAAe,EACf,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,cAAc,GACf,MAAM,WAAW,CAAC"}