@moda-labs/bobi-events-core 0.1.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 (43) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +32 -0
  3. package/dist/adapters/chat-sdk-slack.d.ts +18 -0
  4. package/dist/adapters/chat-sdk-slack.d.ts.map +1 -0
  5. package/dist/adapters/chat-sdk-slack.js +201 -0
  6. package/dist/adapters/chat-sdk-slack.js.map +1 -0
  7. package/dist/adapters/github.d.ts +3 -0
  8. package/dist/adapters/github.d.ts.map +1 -0
  9. package/dist/adapters/github.js +114 -0
  10. package/dist/adapters/github.js.map +1 -0
  11. package/dist/adapters/linear.d.ts +3 -0
  12. package/dist/adapters/linear.d.ts.map +1 -0
  13. package/dist/adapters/linear.js +47 -0
  14. package/dist/adapters/linear.js.map +1 -0
  15. package/dist/adapters/whatsapp.d.ts +25 -0
  16. package/dist/adapters/whatsapp.d.ts.map +1 -0
  17. package/dist/adapters/whatsapp.js +96 -0
  18. package/dist/adapters/whatsapp.js.map +1 -0
  19. package/dist/channels.d.ts +80 -0
  20. package/dist/channels.d.ts.map +1 -0
  21. package/dist/channels.js +499 -0
  22. package/dist/channels.js.map +1 -0
  23. package/dist/circuit-breaker.d.ts +79 -0
  24. package/dist/circuit-breaker.d.ts.map +1 -0
  25. package/dist/circuit-breaker.js +288 -0
  26. package/dist/circuit-breaker.js.map +1 -0
  27. package/dist/conversation.d.ts +29 -0
  28. package/dist/conversation.d.ts.map +1 -0
  29. package/dist/conversation.js +86 -0
  30. package/dist/conversation.js.map +1 -0
  31. package/dist/core.d.ts +254 -0
  32. package/dist/core.d.ts.map +1 -0
  33. package/dist/core.js +1775 -0
  34. package/dist/core.js.map +1 -0
  35. package/package.json +40 -0
  36. package/src/adapters/chat-sdk-slack.ts +209 -0
  37. package/src/adapters/github.ts +111 -0
  38. package/src/adapters/linear.ts +53 -0
  39. package/src/adapters/whatsapp.ts +120 -0
  40. package/src/channels.ts +600 -0
  41. package/src/circuit-breaker.ts +337 -0
  42. package/src/conversation.ts +96 -0
  43. package/src/core.ts +2413 -0
@@ -0,0 +1,499 @@
1
+ /**
2
+ * Channel adapters - outbound contract for the channel gateway (#190 Phase 2).
3
+ *
4
+ * Each chat channel is one adapter implementing this contract, registered in
5
+ * CHANNEL_ADAPTERS. The generic /channels/* handlers in core.ts parse a
6
+ * conversation reference, resolve the sending credential, and delegate here;
7
+ * they never learn platform semantics. Capability degradation is the
8
+ * gateway's job: an `update` on a channel without edit support becomes a
9
+ * follow-up post, `typing` on a channel without indicators is a silent no-op.
10
+ *
11
+ * The Slack adapter is built on the Chat SDK's api module
12
+ * (@chat-adapter/slack/api) - the same package whose webhook parser serves
13
+ * inbound (adapters/chat-sdk-slack.ts). Outbound text arrives as markdown and
14
+ * is delivered natively via Slack's `markdown_text`, so callers never convert
15
+ * client-side.
16
+ */
17
+ import { postSlackMessage, updateSlackMessage, uploadSlackFiles, fetchSlackThreadReplies, callSlackApi, SlackApiError, } from "@chat-adapter/slack/api";
18
+ // Slack Web API base. Overridable so integration tests can stub the Slack
19
+ // API with a local server (local.ts wires BOBI_ES_SLACK_API_URL); production
20
+ // always uses the default. Must end with "/" - the SDK resolves method names
21
+ // relative to it.
22
+ const DEFAULT_SLACK_API_URL = "https://slack.com/api/";
23
+ let slackApiUrlOverride;
24
+ export function setSlackApiUrl(url) {
25
+ slackApiUrlOverride = url ? (url.endsWith("/") ? url : `${url}/`) : undefined;
26
+ }
27
+ export function slackApiUrl() {
28
+ return slackApiUrlOverride ?? DEFAULT_SLACK_API_URL;
29
+ }
30
+ // Meta Graph API base for WhatsApp Cloud API calls. Overridable so tests can
31
+ // stub the Graph API with a local server (local.ts wires
32
+ // BOBI_ES_WHATSAPP_API_URL); production always uses the default.
33
+ const DEFAULT_WHATSAPP_API_URL = "https://graph.facebook.com/v23.0/";
34
+ let whatsappApiUrlOverride;
35
+ export function setWhatsAppApiUrl(url) {
36
+ whatsappApiUrlOverride = url ? (url.endsWith("/") ? url : `${url}/`) : undefined;
37
+ }
38
+ export function whatsappApiUrl() {
39
+ return whatsappApiUrlOverride ?? DEFAULT_WHATSAPP_API_URL;
40
+ }
41
+ /**
42
+ * Outbound budget enforcement (#651). Terminal sends (post / final) go out
43
+ * whole as natural-boundary chunks via chunkForChannel; streaming updates
44
+ * rewrite one message in place, so they stay on truncateForChannel - chunking
45
+ * a rewrite would post a new message on every tick.
46
+ */
47
+ const TRUNCATION_MARKER = "\n_(truncated)_";
48
+ /** Runaway guard: past this many chunks the tail is truncated with the marker. */
49
+ const MAX_CHUNKS = 8;
50
+ /** Appended to a chunk that splits inside a code fence, so it renders closed. */
51
+ const FENCE_CLOSE = "\n```";
52
+ /** Length of `s` in the channel's declared unit. */
53
+ function unitLength(s, caps) {
54
+ if (caps.lengthUnit === "utf16")
55
+ return s.length;
56
+ let n = 0;
57
+ for (const _ch of s)
58
+ n++;
59
+ return n;
60
+ }
61
+ /**
62
+ * UTF-16 index covering at most `units` of `s` in the channel's unit, never
63
+ * splitting a surrogate pair.
64
+ */
65
+ function unitIndex(s, units, caps) {
66
+ if (units <= 0)
67
+ return 0;
68
+ let idx;
69
+ if (caps.lengthUnit === "utf16") {
70
+ idx = Math.min(units, s.length);
71
+ }
72
+ else {
73
+ idx = 0;
74
+ let counted = 0;
75
+ for (const ch of s) {
76
+ if (counted >= units)
77
+ break;
78
+ idx += ch.length;
79
+ counted++;
80
+ }
81
+ }
82
+ // Never split a surrogate pair: if the boundary lands between a high and
83
+ // low surrogate, step back one unit.
84
+ if (idx > 0 && idx < s.length) {
85
+ const cc = s.charCodeAt(idx - 1);
86
+ if (cc >= 0xd800 && cc <= 0xdbff)
87
+ idx--;
88
+ }
89
+ return idx;
90
+ }
91
+ export function truncateForChannel(text, caps) {
92
+ if (unitLength(text, caps) <= caps.maxLength)
93
+ return text;
94
+ // The marker must fit INSIDE the budget - maxLength is the channel's hard
95
+ // limit, and overshooting it fails the whole send (msg_too_long).
96
+ const keep = unitIndex(text, caps.maxLength - TRUNCATION_MARKER.length, caps);
97
+ return text.slice(0, keep) + TRUNCATION_MARKER;
98
+ }
99
+ /**
100
+ * Fence state after scanning `s`. `open` is "" outside a fence, else the
101
+ * fence header to reopen with (e.g. "```python"). Fence lines toggle; an
102
+ * opening line's info string is preserved for reopening. The info string may
103
+ * not contain backticks (per CommonMark), which keeps a line-leading inline
104
+ * span like ```cmd``` from reading as an opener; an implausibly long "info
105
+ * string" (a one-line blob that happens to start with backticks) reopens as
106
+ * a bare fence so the carried prefix can never blow the chunk budget.
107
+ */
108
+ const MAX_FENCE_HEADER = 24;
109
+ function fenceStateAfter(s, open) {
110
+ for (const line of s.split("\n")) {
111
+ const m = line.match(/^\s*(`{3,})([^`]*)$/);
112
+ if (!m)
113
+ continue;
114
+ if (open) {
115
+ open = "";
116
+ }
117
+ else {
118
+ const info = m[2].trim();
119
+ open = info.length > MAX_FENCE_HEADER ? m[1] : m[1] + info;
120
+ }
121
+ }
122
+ return open;
123
+ }
124
+ /**
125
+ * Split over-budget text into channel-sized chunks at natural boundaries:
126
+ * paragraph break, then line break, then a hard (surrogate-safe) cut. A split
127
+ * inside a code fence closes the fence at the chunk end and reopens it (with
128
+ * its info string) at the start of the next chunk, so every chunk renders.
129
+ * Within-budget text comes back as a single chunk, byte-identical.
130
+ */
131
+ export function chunkForChannel(text, caps) {
132
+ if (unitLength(text, caps) <= caps.maxLength)
133
+ return [text];
134
+ const chunks = [];
135
+ let remaining = text;
136
+ let reopen = ""; // fence header carried into the next chunk
137
+ while (chunks.length < MAX_CHUNKS - 1) {
138
+ const prefix = reopen ? reopen + "\n" : "";
139
+ if (unitLength(prefix, caps) + unitLength(remaining, caps) <= caps.maxLength) {
140
+ chunks.push(prefix + remaining);
141
+ return chunks;
142
+ }
143
+ // Reserve room for the prefix and a possible fence close so the chunk
144
+ // never overshoots the hard limit (over-reserving costs a few chars).
145
+ const avail = Math.max(1, caps.maxLength - unitLength(prefix, caps) - unitLength(FENCE_CLOSE, caps));
146
+ const windowEnd = unitIndex(remaining, avail, caps);
147
+ const window = remaining.slice(0, windowEnd);
148
+ // Natural boundaries, but never a blank chunk: a cut that leaves only
149
+ // whitespace (e.g. leading newlines) would fail the whole send.
150
+ let cut = window.lastIndexOf("\n\n");
151
+ if (cut <= 0 || !window.slice(0, cut).trim())
152
+ cut = window.lastIndexOf("\n");
153
+ if (cut <= 0 || !window.slice(0, cut).trim())
154
+ cut = windowEnd; // hard split, surrogate-safe
155
+ const head = remaining.slice(0, cut);
156
+ remaining = remaining.slice(cut).replace(/^\n+/, "");
157
+ reopen = fenceStateAfter(head, reopen);
158
+ let chunk = prefix + head;
159
+ if (reopen)
160
+ chunk += FENCE_CLOSE;
161
+ chunks.push(chunk);
162
+ }
163
+ // Chunk-count guard hit: truncate the tail instead of posting unbounded
164
+ // follow-ups for pathological input, keeping any open fence closed so the
165
+ // final chunk still renders.
166
+ const prefix = reopen ? reopen + "\n" : "";
167
+ let tail = truncateForChannel(prefix + remaining, caps);
168
+ if (fenceStateAfter(tail, "")) {
169
+ tail = truncateForChannel(prefix + remaining, { ...caps, maxLength: caps.maxLength - unitLength(FENCE_CLOSE, caps) }) + FENCE_CLOSE;
170
+ }
171
+ chunks.push(tail);
172
+ return chunks;
173
+ }
174
+ function slackError(err) {
175
+ if (err instanceof SlackApiError) {
176
+ return err.response?.error || err.message;
177
+ }
178
+ return String(err);
179
+ }
180
+ function toConversationMessage(raw) {
181
+ const entry = {
182
+ user: raw.user || "",
183
+ text: raw.text || "",
184
+ ts: raw.ts || "",
185
+ };
186
+ const rawFiles = raw.files;
187
+ if (Array.isArray(rawFiles) && rawFiles.length > 0) {
188
+ entry.files = rawFiles.map((f) => ({
189
+ id: String(f.id ?? ""),
190
+ name: String(f.name ?? ""),
191
+ mimetype: String(f.mimetype ?? ""),
192
+ url_private: String(f.url_private ?? ""),
193
+ }));
194
+ }
195
+ return entry;
196
+ }
197
+ // Message body for a Slack post/update. Raw markdown goes out as Slack's
198
+ // native `markdown_text` (AST-rendered by Slack, 12k budget).
199
+ function slackContent(text) {
200
+ return { markdownText: text };
201
+ }
202
+ const slackAdapter = {
203
+ descriptor: {
204
+ name: "slack",
205
+ topicShape: "slack:<team_id>[:app:<app_id>][:<channel>]",
206
+ transport: "webhook",
207
+ capabilities: {
208
+ edit: true,
209
+ typing: true,
210
+ streaming: "native",
211
+ threads: true,
212
+ files: true,
213
+ // Slack's markdown_text limit; plain text allows ~40k but the
214
+ // gateway sends markdown, so the stricter budget applies. The
215
+ // budget has always been enforced in UTF-16 units (JS .length);
216
+ // declare that unit so chunking keeps the proven behavior.
217
+ maxLength: 12000,
218
+ lengthUnit: "utf16",
219
+ },
220
+ credentials: [
221
+ { env: "SLACK_BOT_TOKEN", secret: true, label: "Bot User OAuth Token" },
222
+ { env: "SLACK_SIGNING_SECRET", secret: true, label: "App Signing Secret" },
223
+ ],
224
+ promptHint: "Slack renders standard markdown. Keep replies concise; use "
225
+ + "triple-backtick blocks for multi-line code.",
226
+ setupSkill: "skills/slack-setup.md",
227
+ },
228
+ async send(token, conv, text) {
229
+ try {
230
+ const posted = await postSlackMessage({
231
+ apiUrl: slackApiUrl(),
232
+ token,
233
+ channel: conv.chatId,
234
+ ...(conv.threadId ? { threadTs: conv.threadId } : {}),
235
+ ...slackContent(text),
236
+ });
237
+ return { ok: true, ts: posted.id };
238
+ }
239
+ catch (err) {
240
+ return { ok: false, error: slackError(err) };
241
+ }
242
+ },
243
+ async update(token, conv, messageId, text) {
244
+ try {
245
+ const posted = await updateSlackMessage({
246
+ apiUrl: slackApiUrl(),
247
+ token,
248
+ channel: conv.chatId,
249
+ ts: messageId,
250
+ ...slackContent(text),
251
+ });
252
+ return { ok: true, ts: posted.id };
253
+ }
254
+ catch (err) {
255
+ return { ok: false, error: slackError(err) };
256
+ }
257
+ },
258
+ // Slack's "typing" is the assistant thread status ("is thinking..."),
259
+ // which only exists in thread context and expires after ~2 minutes -
260
+ // callers refresh while work runs. Best-effort: only DM threads support
261
+ // it, so failures are swallowed.
262
+ async typing(token, conv, on) {
263
+ if (!conv.threadId)
264
+ return;
265
+ try {
266
+ await callSlackApi("assistant.threads.setStatus", {
267
+ channel_id: conv.chatId,
268
+ thread_ts: conv.threadId,
269
+ status: on ? "is thinking…" : "",
270
+ }, { apiUrl: slackApiUrl(), token });
271
+ }
272
+ catch {
273
+ // non-fatal
274
+ }
275
+ },
276
+ async fetchConversation(token, conv, limit = 100) {
277
+ // A ref without a thread anchor reads the channel/DM history instead
278
+ // of thread replies (e.g. a proactive conversation with no thread).
279
+ if (!conv.threadId) {
280
+ const raw = await callSlackApi("conversations.history", {
281
+ channel: conv.chatId,
282
+ limit: Math.min(limit, 200),
283
+ }, { apiUrl: slackApiUrl(), token });
284
+ const rows = raw.messages ?? [];
285
+ // conversations.history returns newest-first; deliver oldest-first
286
+ // to match the thread-replies ordering.
287
+ return rows.map(toConversationMessage).reverse().slice(0, limit);
288
+ }
289
+ const messages = [];
290
+ let cursor;
291
+ while (messages.length < limit) {
292
+ const page = await fetchSlackThreadReplies({
293
+ apiUrl: slackApiUrl(),
294
+ token,
295
+ channel: conv.chatId,
296
+ ts: conv.threadId,
297
+ limit: Math.min(limit - messages.length, 200),
298
+ ...(cursor ? { cursor } : {}),
299
+ });
300
+ for (const raw of page.messages) {
301
+ messages.push(toConversationMessage(raw));
302
+ }
303
+ cursor = page.nextCursor;
304
+ if (!cursor)
305
+ break;
306
+ }
307
+ return messages.slice(0, limit);
308
+ },
309
+ async uploadFiles(token, conv, files, comment) {
310
+ try {
311
+ const result = await uploadSlackFiles(files.map((f) => ({
312
+ data: f.data,
313
+ filename: f.name,
314
+ ...(f.title ? { title: f.title } : {}),
315
+ })), {
316
+ apiUrl: slackApiUrl(),
317
+ token,
318
+ channelId: conv.chatId,
319
+ ...(conv.threadId ? { threadTs: conv.threadId } : {}),
320
+ ...(comment ? { initialComment: comment } : {}),
321
+ });
322
+ return { ok: true, ts: result.fileIds[0] };
323
+ }
324
+ catch (err) {
325
+ return { ok: false, error: slackError(err) };
326
+ }
327
+ },
328
+ };
329
+ // --- WhatsApp (Meta Cloud API) - #656, epic #190 Phase 3 ---
330
+ // One Graph API call. Returns the parsed JSON body; throws on transport
331
+ // errors; a Graph error body ({error: {message}}) is surfaced by callers.
332
+ export async function whatsappApi(token, path, init) {
333
+ const headers = { Authorization: `Bearer ${token}` };
334
+ // Structural (not BodyInit): only these two shapes are ever built, and
335
+ // the name resolves under every lib variant this file compiles against
336
+ // (dom for the published dist, @types/node for the in-repo build).
337
+ let body;
338
+ if (init.json !== undefined) {
339
+ headers["Content-Type"] = "application/json";
340
+ body = JSON.stringify(init.json);
341
+ }
342
+ else if (init.form) {
343
+ body = init.form; // fetch sets the multipart boundary itself
344
+ }
345
+ const resp = await fetch(`${whatsappApiUrl()}${path}`, {
346
+ method: init.method ?? "POST",
347
+ headers,
348
+ ...(body !== undefined ? { body } : {}),
349
+ });
350
+ return (await resp.json());
351
+ }
352
+ // Meta's cap for media/document captions, distinct from the 4096 text limit.
353
+ const WHATSAPP_CAPTION_LIMIT = 1024;
354
+ function whatsappError(data) {
355
+ const err = data.error;
356
+ return err?.message || "whatsapp api error";
357
+ }
358
+ const whatsappAdapter = {
359
+ descriptor: {
360
+ name: "whatsapp",
361
+ topicShape: "whatsapp:<phone_number_id>",
362
+ transport: "webhook",
363
+ capabilities: {
364
+ // No message editing on WhatsApp - the gateway degrades placeholder
365
+ // edits to follow-up posts. No typing indicator, no threads, no
366
+ // native streaming: reactive-only replies (epic #190 Phase 3).
367
+ edit: false,
368
+ typing: false,
369
+ streaming: "none",
370
+ threads: false,
371
+ files: true,
372
+ // Cloud API text body limit, counted in characters (code points) -
373
+ // the unit-aware budget #651 wired in.
374
+ maxLength: 4096,
375
+ lengthUnit: "chars",
376
+ // Meta's customer-service window: replies are free-form for 24h
377
+ // after the user's last inbound message; outside it only template
378
+ // messages send (template support is a follow-up - the gateway
379
+ // returns a typed error so the agent can report the situation).
380
+ messageWindow: { hours: 24, outsideWindow: "template" },
381
+ },
382
+ credentials: [
383
+ { env: "WHATSAPP_ACCESS_TOKEN", secret: true, label: "Cloud API access token" },
384
+ { env: "WHATSAPP_PHONE_NUMBER_ID", secret: false, label: "Phone number ID" },
385
+ { env: "WHATSAPP_APP_SECRET", secret: true, label: "Meta app secret (webhook signatures)" },
386
+ { env: "WHATSAPP_VERIFY_TOKEN", secret: true, label: "Webhook verify token (your choice)" },
387
+ ],
388
+ promptHint: "WhatsApp renders its own light formatting (*bold*, _italic_, "
389
+ + "```monospace```), not full markdown. Keep replies short and "
390
+ + "conversational; avoid headers and tables.",
391
+ setupSkill: "skills/whatsapp-setup.md",
392
+ },
393
+ async send(token, conv, text) {
394
+ try {
395
+ const data = await whatsappApi(token, `${conv.scope}/messages`, {
396
+ json: {
397
+ messaging_product: "whatsapp",
398
+ to: conv.chatId,
399
+ type: "text",
400
+ text: { body: text },
401
+ },
402
+ });
403
+ const id = data.messages?.[0]?.id;
404
+ if (!id)
405
+ return { ok: false, error: whatsappError(data) };
406
+ return { ok: true, ts: id };
407
+ }
408
+ catch (err) {
409
+ return { ok: false, error: String(err) };
410
+ }
411
+ },
412
+ // Two-step Cloud API upload: POST the bytes to /{pnid}/media, then send a
413
+ // document message referencing the media id. Sent as `document` for any
414
+ // mime type - it preserves the filename and works for all content. The
415
+ // media endpoint validates the declared MIME, so it is inferred from the
416
+ // filename (octet-stream is not on Meta's supported list).
417
+ async uploadFiles(token, conv, files, comment) {
418
+ let delivered = 0;
419
+ try {
420
+ let lastId = "";
421
+ for (const [i, f] of files.entries()) {
422
+ const mime = mimeForFilename(f.name);
423
+ const form = new FormData();
424
+ form.append("messaging_product", "whatsapp");
425
+ form.append("type", mime);
426
+ form.append("file", new Blob([f.data], { type: mime }), f.name);
427
+ const uploaded = await whatsappApi(token, `${conv.scope}/media`, { form });
428
+ const mediaId = uploaded.id;
429
+ if (!mediaId)
430
+ return { ok: false, error: partialUploadError(whatsappError(uploaded), i, files.length) };
431
+ // Media captions have their own Cloud API limit (1024 chars),
432
+ // well below the 4096 text-body cap the gateway budget uses.
433
+ const caption = (i === 0 ? comment || f.title || "" : f.title || "")
434
+ .slice(0, WHATSAPP_CAPTION_LIMIT);
435
+ const sent = await whatsappApi(token, `${conv.scope}/messages`, {
436
+ json: {
437
+ messaging_product: "whatsapp",
438
+ to: conv.chatId,
439
+ type: "document",
440
+ document: {
441
+ id: mediaId,
442
+ filename: f.name,
443
+ ...(caption ? { caption } : {}),
444
+ },
445
+ },
446
+ });
447
+ const id = sent.messages?.[0]?.id;
448
+ if (!id)
449
+ return { ok: false, error: partialUploadError(whatsappError(sent), i, files.length) };
450
+ lastId = id;
451
+ delivered = i + 1;
452
+ }
453
+ return { ok: true, ts: lastId };
454
+ }
455
+ catch (err) {
456
+ return { ok: false, error: partialUploadError(String(err), delivered, files.length) };
457
+ }
458
+ },
459
+ };
460
+ // Mirror the chunk path's partial-delivery contract: once any file reached
461
+ // the user, the error must tell the caller not to blind-retry the batch.
462
+ function partialUploadError(error, delivered, total) {
463
+ if (delivered === 0)
464
+ return error;
465
+ return `file ${delivered + 1}/${total} failed after partial delivery `
466
+ + `(do not resend; ${delivered} file(s) are already visible): ${error}`;
467
+ }
468
+ // Minimal extension map for the document types agents actually send. Meta's
469
+ // media endpoint rejects MIME types outside its supported list
470
+ // (application/octet-stream is not on it), so unknowns go out as text/plain.
471
+ const MIME_BY_EXT = {
472
+ pdf: "application/pdf",
473
+ txt: "text/plain",
474
+ md: "text/plain",
475
+ csv: "text/csv",
476
+ png: "image/png",
477
+ jpg: "image/jpeg",
478
+ jpeg: "image/jpeg",
479
+ webp: "image/webp",
480
+ mp4: "video/mp4",
481
+ doc: "application/msword",
482
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
483
+ xls: "application/vnd.ms-excel",
484
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
485
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
486
+ zip: "application/zip",
487
+ };
488
+ function mimeForFilename(name) {
489
+ const ext = name.toLowerCase().split(".").pop() || "";
490
+ return MIME_BY_EXT[ext] || "text/plain";
491
+ }
492
+ const CHANNEL_ADAPTERS = {
493
+ slack: slackAdapter,
494
+ whatsapp: whatsappAdapter,
495
+ };
496
+ export function getChannelAdapter(source) {
497
+ return CHANNEL_ADAPTERS[source];
498
+ }
499
+ //# sourceMappingURL=channels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channels.js","sourceRoot":"","sources":["../src/channels.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Delivery-path circuit breaker (#299, split from #215 loop-safety).
3
+ *
4
+ * Keying decision: the breaker keys on `(deployment_id, conversation)` where
5
+ * conversation is derived from external channel identity:
6
+ * - Slack: thread_ts (or ts if no thread)
7
+ * - GitHub: repo + issue/PR number
8
+ * - Other: event type (coarse — acceptable because custom events rarely loop)
9
+ *
10
+ * EXEMPTIONS: `inbox/*` and `reply/*` topics are legitimate internal
11
+ * agent↔agent comms and are NEVER counted toward the breaker. They flow
12
+ * through a separate routing namespace (`agent/*` topics) that does not
13
+ * represent external conversation depth.
14
+ *
15
+ * Trip condition: ≥ THRESHOLD non-human-authored deliveries in one key within
16
+ * WINDOW_MS with zero human events → pause delivery for that key (buffered,
17
+ * not dropped). Emit `system.loop_detected`. Auto-resume after COOLDOWN_MS or
18
+ * on the next human-authored event in the same key.
19
+ */
20
+ import type { NormalizedEvent } from "./core.js";
21
+ export declare const BREAKER_THRESHOLD = 5;
22
+ export declare const BREAKER_WINDOW_MS = 60000;
23
+ export declare const BREAKER_COOLDOWN_MS = 300000;
24
+ export interface BreakerState {
25
+ /** Timestamps of non-human deliveries within the current window */
26
+ timestamps: number[];
27
+ /** Whether the breaker is currently tripped */
28
+ tripped: boolean;
29
+ /** When the breaker was tripped (epoch ms), undefined if not tripped */
30
+ trippedAt?: number;
31
+ /** Events paused while tripped — delivered on resume */
32
+ paused: NormalizedEvent[];
33
+ }
34
+ export interface BreakerVerdict {
35
+ /** Whether delivery should proceed */
36
+ allow: boolean;
37
+ /** Whether this delivery tripped the breaker (first trip) */
38
+ justTripped: boolean;
39
+ }
40
+ /**
41
+ * Returns true if this event's type is exempt from breaker counting.
42
+ * inbox/* and reply/* are internal agent↔agent comms, not external
43
+ * conversation depth.
44
+ */
45
+ export declare function isExemptFromBreaker(event: NormalizedEvent): boolean;
46
+ /**
47
+ * Extract the conversation key from an event. Returns null if no meaningful
48
+ * conversation can be identified (breaker won't apply).
49
+ */
50
+ export declare function conversationKey(event: NormalizedEvent): string | null;
51
+ /**
52
+ * Determine whether an event is authored by a bot (non-human).
53
+ * Human-authored events reset the breaker window.
54
+ */
55
+ export declare function isBotAuthored(event: NormalizedEvent): boolean;
56
+ /**
57
+ * Record a delivery attempt and return a verdict.
58
+ *
59
+ * Call this BEFORE actually delivering the event. If `allow` is false,
60
+ * the caller must buffer the event (it is already added to state.paused).
61
+ */
62
+ export declare function recordDelivery(deploymentId: string, event: NormalizedEvent): BreakerVerdict;
63
+ /**
64
+ * Drain paused events for a deployment+conversation. Returns the events
65
+ * that were buffered and clears the pause buffer. The caller is responsible
66
+ * for delivering them.
67
+ */
68
+ export declare function drainPaused(deploymentId: string, event: NormalizedEvent): NormalizedEvent[];
69
+ /**
70
+ * Check whether a breaker is currently tripped for a deployment+conversation.
71
+ * Used after cooldown to decide whether to flush paused events.
72
+ */
73
+ export declare function isBreakerTripped(deploymentId: string, convKey: string): boolean;
74
+ /**
75
+ * Build the system.loop_detected event to be published.
76
+ */
77
+ export declare function buildLoopDetectedEvent(deploymentId: string, convKey: string, triggerEvent: NormalizedEvent): NormalizedEvent;
78
+ export declare function resetAllBreakers(): void;
79
+ //# sourceMappingURL=circuit-breaker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circuit-breaker.d.ts","sourceRoot":"","sources":["../src/circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAMjD,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,iBAAiB,QAAS,CAAC;AACxC,eAAO,MAAM,mBAAmB,SAAU,CAAC;AAM3C,MAAM,WAAW,YAAY;IAC5B,mEAAmE;IACnE,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,MAAM,EAAE,eAAe,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC9B,sCAAsC;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,6DAA6D;IAC7D,WAAW,EAAE,OAAO,CAAC;CACrB;AAaD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAanE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAmDrE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CA4B7D;AAkCD;;;;;GAKG;AACH,wBAAgB,cAAc,CAC7B,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,eAAe,GACpB,cAAc,CAsChB;AAaD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,eAAe,EAAE,CAQ3F;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAS/E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACrC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,eAAe,GAC3B,eAAe,CAyBjB;AAMD,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}