@cogenta/channels 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 (177) hide show
  1. package/dist/adapter.d.ts +111 -0
  2. package/dist/adapter.d.ts.map +1 -0
  3. package/dist/adapter.js +2 -0
  4. package/dist/adapter.js.map +1 -0
  5. package/dist/approvals/commands.d.ts +28 -0
  6. package/dist/approvals/commands.d.ts.map +1 -0
  7. package/dist/approvals/commands.js +66 -0
  8. package/dist/approvals/commands.js.map +1 -0
  9. package/dist/approvals/dispatch.d.ts +22 -0
  10. package/dist/approvals/dispatch.d.ts.map +1 -0
  11. package/dist/approvals/dispatch.js +22 -0
  12. package/dist/approvals/dispatch.js.map +1 -0
  13. package/dist/approvals/message.d.ts +18 -0
  14. package/dist/approvals/message.d.ts.map +1 -0
  15. package/dist/approvals/message.js +40 -0
  16. package/dist/approvals/message.js.map +1 -0
  17. package/dist/approvals/signed-link.d.ts +19 -0
  18. package/dist/approvals/signed-link.d.ts.map +1 -0
  19. package/dist/approvals/signed-link.js +42 -0
  20. package/dist/approvals/signed-link.js.map +1 -0
  21. package/dist/approvals/store.d.ts +41 -0
  22. package/dist/approvals/store.d.ts.map +1 -0
  23. package/dist/approvals/store.js +41 -0
  24. package/dist/approvals/store.js.map +1 -0
  25. package/dist/approvals/token.d.ts +7 -0
  26. package/dist/approvals/token.d.ts.map +1 -0
  27. package/dist/approvals/token.js +40 -0
  28. package/dist/approvals/token.js.map +1 -0
  29. package/dist/formats/alert.d.ts +18 -0
  30. package/dist/formats/alert.d.ts.map +1 -0
  31. package/dist/formats/alert.js +49 -0
  32. package/dist/formats/alert.js.map +1 -0
  33. package/dist/formats/budget.d.ts +14 -0
  34. package/dist/formats/budget.d.ts.map +1 -0
  35. package/dist/formats/budget.js +13 -0
  36. package/dist/formats/budget.js.map +1 -0
  37. package/dist/formats/notification.d.ts +8 -0
  38. package/dist/formats/notification.d.ts.map +1 -0
  39. package/dist/formats/notification.js +26 -0
  40. package/dist/formats/notification.js.map +1 -0
  41. package/dist/formats/report.d.ts +18 -0
  42. package/dist/formats/report.d.ts.map +1 -0
  43. package/dist/formats/report.js +44 -0
  44. package/dist/formats/report.js.map +1 -0
  45. package/dist/inbound/authorize.d.ts +57 -0
  46. package/dist/inbound/authorize.d.ts.map +1 -0
  47. package/dist/inbound/authorize.js +32 -0
  48. package/dist/inbound/authorize.js.map +1 -0
  49. package/dist/inbound/router.d.ts +67 -0
  50. package/dist/inbound/router.d.ts.map +1 -0
  51. package/dist/inbound/router.js +69 -0
  52. package/dist/inbound/router.js.map +1 -0
  53. package/dist/index.d.ts +77 -0
  54. package/dist/index.d.ts.map +1 -0
  55. package/dist/index.js +42 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/linking/codes.d.ts +6 -0
  58. package/dist/linking/codes.d.ts.map +1 -0
  59. package/dist/linking/codes.js +37 -0
  60. package/dist/linking/codes.js.map +1 -0
  61. package/dist/linking/store.d.ts +39 -0
  62. package/dist/linking/store.d.ts.map +1 -0
  63. package/dist/linking/store.js +77 -0
  64. package/dist/linking/store.js.map +1 -0
  65. package/dist/linking/tables.d.ts +14 -0
  66. package/dist/linking/tables.d.ts.map +1 -0
  67. package/dist/linking/tables.js +52 -0
  68. package/dist/linking/tables.js.map +1 -0
  69. package/dist/preferences/dispatcher.d.ts +37 -0
  70. package/dist/preferences/dispatcher.d.ts.map +1 -0
  71. package/dist/preferences/dispatcher.js +103 -0
  72. package/dist/preferences/dispatcher.js.map +1 -0
  73. package/dist/preferences/quiet-hours.d.ts +12 -0
  74. package/dist/preferences/quiet-hours.d.ts.map +1 -0
  75. package/dist/preferences/quiet-hours.js +22 -0
  76. package/dist/preferences/quiet-hours.js.map +1 -0
  77. package/dist/preferences/store.d.ts +8 -0
  78. package/dist/preferences/store.d.ts.map +1 -0
  79. package/dist/preferences/store.js +60 -0
  80. package/dist/preferences/store.js.map +1 -0
  81. package/dist/preferences/tables.d.ts +12 -0
  82. package/dist/preferences/tables.d.ts.map +1 -0
  83. package/dist/preferences/tables.js +55 -0
  84. package/dist/preferences/tables.js.map +1 -0
  85. package/dist/preferences/types.d.ts +37 -0
  86. package/dist/preferences/types.d.ts.map +1 -0
  87. package/dist/preferences/types.js +31 -0
  88. package/dist/preferences/types.js.map +1 -0
  89. package/dist/providers/discord/adapter.d.ts +38 -0
  90. package/dist/providers/discord/adapter.d.ts.map +1 -0
  91. package/dist/providers/discord/adapter.js +97 -0
  92. package/dist/providers/discord/adapter.js.map +1 -0
  93. package/dist/providers/discord/client.d.ts +67 -0
  94. package/dist/providers/discord/client.d.ts.map +1 -0
  95. package/dist/providers/discord/client.js +65 -0
  96. package/dist/providers/discord/client.js.map +1 -0
  97. package/dist/providers/discord/gateway.d.ts +55 -0
  98. package/dist/providers/discord/gateway.d.ts.map +1 -0
  99. package/dist/providers/discord/gateway.js +114 -0
  100. package/dist/providers/discord/gateway.js.map +1 -0
  101. package/dist/providers/discord/inbound.d.ts +22 -0
  102. package/dist/providers/discord/inbound.d.ts.map +1 -0
  103. package/dist/providers/discord/inbound.js +75 -0
  104. package/dist/providers/discord/inbound.js.map +1 -0
  105. package/dist/providers/discord/render.d.ts +9 -0
  106. package/dist/providers/discord/render.d.ts.map +1 -0
  107. package/dist/providers/discord/render.js +73 -0
  108. package/dist/providers/discord/render.js.map +1 -0
  109. package/dist/providers/email/adapter.d.ts +18 -0
  110. package/dist/providers/email/adapter.d.ts.map +1 -0
  111. package/dist/providers/email/adapter.js +41 -0
  112. package/dist/providers/email/adapter.js.map +1 -0
  113. package/dist/providers/email/file-transport.d.ts +14 -0
  114. package/dist/providers/email/file-transport.d.ts.map +1 -0
  115. package/dist/providers/email/file-transport.js +33 -0
  116. package/dist/providers/email/file-transport.js.map +1 -0
  117. package/dist/providers/email/render.d.ts +20 -0
  118. package/dist/providers/email/render.d.ts.map +1 -0
  119. package/dist/providers/email/render.js +89 -0
  120. package/dist/providers/email/render.js.map +1 -0
  121. package/dist/providers/email/transport.d.ts +25 -0
  122. package/dist/providers/email/transport.d.ts.map +1 -0
  123. package/dist/providers/email/transport.js +2 -0
  124. package/dist/providers/email/transport.js.map +1 -0
  125. package/dist/providers/slack/adapter.d.ts +35 -0
  126. package/dist/providers/slack/adapter.d.ts.map +1 -0
  127. package/dist/providers/slack/adapter.js +91 -0
  128. package/dist/providers/slack/adapter.js.map +1 -0
  129. package/dist/providers/slack/client.d.ts +45 -0
  130. package/dist/providers/slack/client.d.ts.map +1 -0
  131. package/dist/providers/slack/client.js +54 -0
  132. package/dist/providers/slack/client.js.map +1 -0
  133. package/dist/providers/slack/inbound.d.ts +23 -0
  134. package/dist/providers/slack/inbound.d.ts.map +1 -0
  135. package/dist/providers/slack/inbound.js +76 -0
  136. package/dist/providers/slack/inbound.js.map +1 -0
  137. package/dist/providers/slack/render.d.ts +8 -0
  138. package/dist/providers/slack/render.d.ts.map +1 -0
  139. package/dist/providers/slack/render.js +72 -0
  140. package/dist/providers/slack/render.js.map +1 -0
  141. package/dist/providers/slack/socket.d.ts +34 -0
  142. package/dist/providers/slack/socket.d.ts.map +1 -0
  143. package/dist/providers/slack/socket.js +70 -0
  144. package/dist/providers/slack/socket.js.map +1 -0
  145. package/dist/providers/telegram/adapter.d.ts +35 -0
  146. package/dist/providers/telegram/adapter.d.ts.map +1 -0
  147. package/dist/providers/telegram/adapter.js +109 -0
  148. package/dist/providers/telegram/adapter.js.map +1 -0
  149. package/dist/providers/telegram/client.d.ts +78 -0
  150. package/dist/providers/telegram/client.d.ts.map +1 -0
  151. package/dist/providers/telegram/client.js +53 -0
  152. package/dist/providers/telegram/client.js.map +1 -0
  153. package/dist/providers/telegram/inbound.d.ts +21 -0
  154. package/dist/providers/telegram/inbound.d.ts.map +1 -0
  155. package/dist/providers/telegram/inbound.js +72 -0
  156. package/dist/providers/telegram/inbound.js.map +1 -0
  157. package/dist/providers/telegram/render.d.ts +15 -0
  158. package/dist/providers/telegram/render.d.ts.map +1 -0
  159. package/dist/providers/telegram/render.js +87 -0
  160. package/dist/providers/telegram/render.js.map +1 -0
  161. package/dist/providers/webhook/adapter.d.ts +40 -0
  162. package/dist/providers/webhook/adapter.d.ts.map +1 -0
  163. package/dist/providers/webhook/adapter.js +71 -0
  164. package/dist/providers/webhook/adapter.js.map +1 -0
  165. package/dist/providers/webhook/render.d.ts +26 -0
  166. package/dist/providers/webhook/render.d.ts.map +1 -0
  167. package/dist/providers/webhook/render.js +41 -0
  168. package/dist/providers/webhook/render.js.map +1 -0
  169. package/dist/providers/webhook/signing.d.ts +59 -0
  170. package/dist/providers/webhook/signing.d.ts.map +1 -0
  171. package/dist/providers/webhook/signing.js +108 -0
  172. package/dist/providers/webhook/signing.js.map +1 -0
  173. package/dist/registry.d.ts +16 -0
  174. package/dist/registry.d.ts.map +1 -0
  175. package/dist/registry.js +39 -0
  176. package/dist/registry.js.map +1 -0
  177. package/package.json +42 -0
@@ -0,0 +1,37 @@
1
+ import type { ChannelSeverity } from '../adapter.js';
2
+ /**
3
+ * An open, but real, typed set of event kinds a producer (an approval
4
+ * dispatch, a future SEO/security/performance agent report) can notify
5
+ * about — "quels types d'événements" (lot doc) needs something a user can
6
+ * actually opt into, not a bare unvalidated string.
7
+ */
8
+ export declare const CHANNEL_EVENT_TYPES: readonly ['approval-request', 'security-alert', 'seo-report', 'performance-report', 'content-report'];
9
+ export type ChannelEventType = (typeof CHANNEL_EVENT_TYPES)[number];
10
+ export type GroupingMode = 'immediate' | 'hourly' | 'daily';
11
+ /** Minutes since local midnight, `0..1439`. */
12
+ export interface QuietHours {
13
+ readonly startMinute: number;
14
+ readonly endMinute: number;
15
+ }
16
+ /**
17
+ * "Par utilisateur et par canal : quels types d'événements, quelle gravité
18
+ * minimale, quelle plage horaire, quel regroupement." One row per
19
+ * `(userId, channelName)` — the same user can want immediate critical
20
+ * alerts on one channel and a daily digest on another.
21
+ */
22
+ export interface ChannelPreferences {
23
+ readonly eventTypes: readonly ChannelEventType[];
24
+ readonly minSeverity: ChannelSeverity;
25
+ /** `null` — no quiet hours configured, nothing is ever deferred by time of day. */
26
+ readonly quietHours: QuietHours | null;
27
+ readonly grouping: GroupingMode;
28
+ }
29
+ /**
30
+ * The safe default for a `(userId, channelName)` with no stored row —
31
+ * "receive everything immediately" until the user tunes it down, matching
32
+ * this codebase's R2-style rule that an unconfigured state must still work,
33
+ * never silently drop something the user never asked to suppress.
34
+ */
35
+ export declare const DEFAULT_CHANNEL_PREFERENCES: ChannelPreferences;
36
+ export declare const SEVERITY_RANK: Readonly<Record<ChannelSeverity, number>>;
37
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/preferences/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,YAC9B,kBAAkB,EAClB,gBAAgB,EAChB,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,CACR,CAAA;AAEV,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEnE,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE3D,+CAA+C;AAC/C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAChD,QAAQ,CAAC,WAAW,EAAE,eAAe,CAAA;IACrC,mFAAmF;IACnF,QAAQ,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CAAA;IACtC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAA;CAChC;AAED;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,EAAE,kBAKzC,CAAA;AAED,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAInE,CAAA"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * An open, but real, typed set of event kinds a producer (an approval
3
+ * dispatch, a future SEO/security/performance agent report) can notify
4
+ * about — "quels types d'événements" (lot doc) needs something a user can
5
+ * actually opt into, not a bare unvalidated string.
6
+ */
7
+ export const CHANNEL_EVENT_TYPES = [
8
+ 'approval-request',
9
+ 'security-alert',
10
+ 'seo-report',
11
+ 'performance-report',
12
+ 'content-report',
13
+ ];
14
+ /**
15
+ * The safe default for a `(userId, channelName)` with no stored row —
16
+ * "receive everything immediately" until the user tunes it down, matching
17
+ * this codebase's R2-style rule that an unconfigured state must still work,
18
+ * never silently drop something the user never asked to suppress.
19
+ */
20
+ export const DEFAULT_CHANNEL_PREFERENCES = {
21
+ eventTypes: CHANNEL_EVENT_TYPES,
22
+ minSeverity: 'info',
23
+ quietHours: null,
24
+ grouping: 'immediate',
25
+ };
26
+ export const SEVERITY_RANK = {
27
+ info: 0,
28
+ warning: 1,
29
+ critical: 2,
30
+ };
31
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/preferences/types.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,kBAAkB;IAClB,gBAAgB;IAChB,YAAY;IACZ,oBAAoB;IACpB,gBAAgB;CACR,CAAA;AA0BV;;;;;GAKG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAuB;IAC7D,UAAU,EAAE,mBAAmB;IAC/B,WAAW,EAAE,MAAM;IACnB,UAAU,EAAE,IAAI;IAChB,QAAQ,EAAE,WAAW;CACtB,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAA8C;IACtE,IAAI,EAAE,CAAC;IACP,OAAO,EAAE,CAAC;IACV,QAAQ,EAAE,CAAC;CACZ,CAAA"}
@@ -0,0 +1,38 @@
1
+ import type { ChannelAdapter, ChannelMessage, InboundHandler, MessageId } from '../../adapter.js';
2
+ import type { CommandRouter } from '../../inbound/router.js';
3
+ import type { ChannelLinkStore } from '../../linking/store.js';
4
+ import { type DiscordClientConfig } from './client.js';
5
+ import { type DiscordGatewayClient } from './gateway.js';
6
+ export interface DiscordLinkProof {
7
+ readonly code: string;
8
+ readonly channelUserId: string;
9
+ }
10
+ export interface DiscordAdapterOptions extends DiscordClientConfig {
11
+ readonly linkStore: ChannelLinkStore;
12
+ readonly router: CommandRouter;
13
+ readonly intents?: number;
14
+ readonly gatewayClient?: DiscordGatewayClient;
15
+ }
16
+ /**
17
+ * A live channel adapter beyond the generic interface: `start()`/`stop()`
18
+ * manage the Gateway connection, which has no counterpart in
19
+ * `ChannelAdapter` — same shape as `TelegramAdapter`'s polling lifecycle and
20
+ * `SlackAdapter`'s Socket Mode lifecycle.
21
+ */
22
+ export interface DiscordAdapter extends ChannelAdapter {
23
+ update(id: MessageId, message: ChannelMessage): Promise<void>;
24
+ onInbound(handler: InboundHandler): void;
25
+ start(): Promise<void>;
26
+ stop(): void;
27
+ }
28
+ /**
29
+ * "Adaptateur Discord." The Gateway (a persistent WebSocket), not the
30
+ * Interactions Endpoint URL webhook — Discord offers no polling REST
31
+ * alternative for real-time messages (unlike Telegram's `getUpdates`), and
32
+ * the webhook path needs a public HTTPS endpoint plus Ed25519 signature
33
+ * verification this project has nowhere to receive yet (no plane of this
34
+ * project is deployed publicly). The Gateway is Discord's own real,
35
+ * officially-supported answer to "no public URL."
36
+ */
37
+ export declare function createDiscordAdapter(options: DiscordAdapterOptions): DiscordAdapter;
38
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/providers/discord/adapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAEd,cAAc,EAEd,cAAc,EACd,SAAS,EACV,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,EAA2C,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAC/F,OAAO,EAA8B,KAAK,oBAAoB,EAAE,MAAM,cAAc,CAAA;AASpF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAC/B;AAWD,MAAM,WAAW,qBAAsB,SAAQ,mBAAmB;IAChE,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,aAAa,CAAC,EAAE,oBAAoB,CAAA;CAC9C;AAED;;;;;GAKG;AACH,MAAM,WAAW,cAAe,SAAQ,cAAc;IACpD,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7D,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAAA;IACxC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,IAAI,IAAI,CAAA;CACb;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAkFnF"}
@@ -0,0 +1,97 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ import { createDiscordClient } from './client.js';
3
+ import { createDiscordGatewayClient } from './gateway.js';
4
+ import { createDiscordInboundHandler } from './inbound.js';
5
+ import { renderDiscordMessage } from './render.js';
6
+ const CHANNEL_NAME = 'discord';
7
+ /** `GUILD_MESSAGES | MESSAGE_CONTENT` — the minimum needed to read `MESSAGE_CREATE.content`; no more requested than this adapter actually uses. */
8
+ const DEFAULT_INTENTS = (1 << 9) | (1 << 15);
9
+ function isDiscordLinkProof(proof) {
10
+ return (typeof proof === 'object' &&
11
+ proof !== null &&
12
+ typeof proof.code === 'string' &&
13
+ typeof proof.channelUserId === 'string');
14
+ }
15
+ /**
16
+ * "Adaptateur Discord." The Gateway (a persistent WebSocket), not the
17
+ * Interactions Endpoint URL webhook — Discord offers no polling REST
18
+ * alternative for real-time messages (unlike Telegram's `getUpdates`), and
19
+ * the webhook path needs a public HTTPS endpoint plus Ed25519 signature
20
+ * verification this project has nowhere to receive yet (no plane of this
21
+ * project is deployed publicly). The Gateway is Discord's own real,
22
+ * officially-supported answer to "no public URL."
23
+ */
24
+ export function createDiscordAdapter(options) {
25
+ const client = createDiscordClient(options);
26
+ const gateway = options.gatewayClient ??
27
+ createDiscordGatewayClient({
28
+ token: options.botToken,
29
+ intents: options.intents ?? DEFAULT_INTENTS,
30
+ });
31
+ let inboundHandler;
32
+ const handleDispatch = createDiscordInboundHandler({
33
+ client,
34
+ linkStore: options.linkStore,
35
+ router: options.router,
36
+ getExternalHandler: () => inboundHandler,
37
+ });
38
+ return {
39
+ name: CHANNEL_NAME,
40
+ capabilities: {
41
+ richText: true,
42
+ buttons: true,
43
+ threads: false,
44
+ attachments: false,
45
+ inbound: true,
46
+ },
47
+ async send(target, message) {
48
+ const rendered = renderDiscordMessage(message);
49
+ const sent = await client.sendMessage({
50
+ channelId: target.id,
51
+ ...(rendered.content === undefined ? {} : { content: rendered.content }),
52
+ ...(rendered.embeds.length > 0 ? { embeds: rendered.embeds } : {}),
53
+ ...(rendered.components.length > 0 ? { components: rendered.components } : {}),
54
+ });
55
+ return `${sent.channelId}:${sent.messageId}`;
56
+ },
57
+ async update(id, message) {
58
+ const [channelId, messageId] = id.split(':');
59
+ if (channelId === undefined || messageId === undefined) {
60
+ throw new CogentaError({
61
+ code: 'CHANNEL_DISCORD_API_ERROR',
62
+ message: `"${id}" is not a message id this adapter produced.`,
63
+ hint: "Only pass a MessageId returned by this adapter's own send() to update().",
64
+ });
65
+ }
66
+ const rendered = renderDiscordMessage(message);
67
+ await client.updateMessage({
68
+ channelId,
69
+ messageId,
70
+ ...(rendered.content === undefined ? {} : { content: rendered.content }),
71
+ ...(rendered.embeds.length > 0 ? { embeds: rendered.embeds } : {}),
72
+ ...(rendered.components.length > 0 ? { components: rendered.components } : {}),
73
+ });
74
+ },
75
+ onInbound(handler) {
76
+ inboundHandler = handler;
77
+ },
78
+ async verifyIdentity(proof) {
79
+ if (!isDiscordLinkProof(proof)) {
80
+ throw new CogentaError({
81
+ code: 'CHANNEL_DISCORD_API_ERROR',
82
+ message: 'Discord identity proof must be { code, channelUserId }.',
83
+ hint: 'Pass the linking code submitted in the channel and the Discord user id it came from.',
84
+ });
85
+ }
86
+ await options.linkStore.verifyCode(proof.code, CHANNEL_NAME, proof.channelUserId);
87
+ return options.linkStore.resolveIdentity(CHANNEL_NAME, proof.channelUserId);
88
+ },
89
+ async start() {
90
+ await gateway.connect(handleDispatch);
91
+ },
92
+ stop() {
93
+ gateway.disconnect();
94
+ },
95
+ };
96
+ }
97
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../src/providers/discord/adapter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * A minimal, hand-typed client for the Discord REST methods this adapter
3
+ * needs to send/edit a message and acknowledge a component interaction —
4
+ * plain HTTPS/JSON, same zero-dependency reasoning as
5
+ * `providers/telegram/client.ts` and `providers/slack/client.ts`.
6
+ */
7
+ export interface DiscordEmbedField {
8
+ readonly name: string;
9
+ readonly value: string;
10
+ readonly inline?: boolean;
11
+ }
12
+ export interface DiscordEmbed {
13
+ readonly title?: string;
14
+ readonly description?: string;
15
+ readonly url?: string;
16
+ readonly color?: number;
17
+ readonly fields?: readonly DiscordEmbedField[];
18
+ }
19
+ export interface DiscordButtonComponent {
20
+ readonly type: 2;
21
+ readonly style: 1;
22
+ readonly label: string;
23
+ readonly custom_id: string;
24
+ }
25
+ export interface DiscordActionRow {
26
+ readonly type: 1;
27
+ readonly components: readonly DiscordButtonComponent[];
28
+ }
29
+ export interface DiscordSendMessageParams {
30
+ readonly channelId: string;
31
+ readonly content?: string;
32
+ readonly embeds?: readonly DiscordEmbed[];
33
+ readonly components?: readonly DiscordActionRow[];
34
+ }
35
+ export interface DiscordUpdateMessageParams {
36
+ readonly channelId: string;
37
+ readonly messageId: string;
38
+ readonly content?: string;
39
+ readonly embeds?: readonly DiscordEmbed[];
40
+ readonly components?: readonly DiscordActionRow[];
41
+ }
42
+ export interface DiscordMessageResult {
43
+ readonly channelId: string;
44
+ readonly messageId: string;
45
+ }
46
+ export interface DiscordClientConfig {
47
+ readonly botToken: string;
48
+ /** Overridable for tests; defaults to the real Discord API host. */
49
+ readonly baseUrl?: string;
50
+ readonly fetchImpl?: typeof fetch;
51
+ /** Overridable for tests, so a 429 retry doesn't really wait. */
52
+ readonly sleepImpl?: (ms: number) => Promise<void>;
53
+ }
54
+ export interface DiscordClient {
55
+ sendMessage(params: DiscordSendMessageParams): Promise<DiscordMessageResult>;
56
+ updateMessage(params: DiscordUpdateMessageParams): Promise<void>;
57
+ /**
58
+ * Discord requires an interaction be acknowledged within 3 seconds or the
59
+ * component shows "This interaction failed" to the user — a deferred
60
+ * update (type 6, `DEFERRED_UPDATE_MESSAGE`) acks with no visible change;
61
+ * any real reply this adapter needs to send goes through a normal
62
+ * `sendMessage` call afterwards, same shape as Telegram/Slack's reply flow.
63
+ */
64
+ acknowledgeInteraction(interactionId: string, interactionToken: string): Promise<void>;
65
+ }
66
+ export declare function createDiscordClient(config: DiscordClientConfig): DiscordClient;
67
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/providers/discord/client.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAC/C;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;IAChB,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAA;IACjB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAA;IAChB,QAAQ,CAAC,UAAU,EAAE,SAAS,sBAAsB,EAAE,CAAA;CACvD;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,CAAA;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAClD;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,CAAA;IACzC,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAA;CAClD;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAkBD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,oEAAoE;IACpE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;IACjC,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACnD;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,CAAC,MAAM,EAAE,wBAAwB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAA;IAC5E,aAAa,CAAC,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChE;;;;;;OAMG;IACH,sBAAsB,CAAC,aAAa,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACvF;AASD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,aAAa,CAkF9E"}
@@ -0,0 +1,65 @@
1
+ import { CogentaError } from '@cogenta/core';
2
+ const DEFAULT_BASE_URL = 'https://discord.com/api/v10';
3
+ const DEFERRED_UPDATE_MESSAGE = 6;
4
+ function defaultSleep(ms) {
5
+ return new Promise((resolve) => setTimeout(resolve, ms));
6
+ }
7
+ export function createDiscordClient(config) {
8
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
9
+ const fetchImpl = config.fetchImpl ?? fetch;
10
+ const sleepImpl = config.sleepImpl ?? defaultSleep;
11
+ async function call(method, path, body, expectBody = true) {
12
+ for (;;) {
13
+ const response = await fetchImpl(`${baseUrl}${path}`, {
14
+ method,
15
+ headers: {
16
+ 'content-type': 'application/json',
17
+ authorization: `Bot ${config.botToken}`,
18
+ },
19
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
20
+ });
21
+ if (response.status === 429) {
22
+ const rateLimit = (await response.json());
23
+ const retryAfterSeconds = Number.isFinite(rateLimit.retry_after) ? rateLimit.retry_after : 1;
24
+ await sleepImpl(retryAfterSeconds * 1000);
25
+ continue;
26
+ }
27
+ if (!response.ok) {
28
+ const error = (await response.json());
29
+ throw new CogentaError({
30
+ code: 'CHANNEL_DISCORD_API_ERROR',
31
+ message: `Discord API call "${method} ${path}" failed: ${error.message}`,
32
+ hint: 'Check the bot token, that it was invited to the guild, and that the channel/message this call references still exists.',
33
+ details: { method, path, discordCode: error.code, error: error.message },
34
+ });
35
+ }
36
+ if (!expectBody)
37
+ return undefined;
38
+ return (await response.json());
39
+ }
40
+ }
41
+ return {
42
+ async sendMessage(params) {
43
+ const result = await call('POST', `/channels/${params.channelId}/messages`, {
44
+ content: params.content,
45
+ embeds: params.embeds,
46
+ components: params.components,
47
+ });
48
+ if (result === undefined) {
49
+ throw new CogentaError({
50
+ code: 'CHANNEL_DISCORD_API_ERROR',
51
+ message: 'Discord did not return the sent message.',
52
+ hint: 'This should not happen — the send-message endpoint always returns the created message.',
53
+ });
54
+ }
55
+ return { channelId: result.channel_id, messageId: result.id };
56
+ },
57
+ async updateMessage(params) {
58
+ await call('PATCH', `/channels/${params.channelId}/messages/${params.messageId}`, { content: params.content, embeds: params.embeds, components: params.components }, false);
59
+ },
60
+ async acknowledgeInteraction(interactionId, interactionToken) {
61
+ await call('POST', `/interactions/${interactionId}/${interactionToken}/callback`, { type: DEFERRED_UPDATE_MESSAGE }, false);
62
+ },
63
+ };
64
+ }
65
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/providers/discord/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Discord's real-time Gateway — a persistent WebSocket, the only real
3
+ * mechanism for a bot with no public HTTPS endpoint to receive messages and
4
+ * interactions (unlike Telegram, Discord has no polling REST alternative;
5
+ * the alternative, an "Interactions Endpoint URL" webhook, needs a public
6
+ * HTTPS endpoint plus Ed25519 signature verification — the same "no public
7
+ * plane deployed" problem `providers/telegram/adapter.ts` and
8
+ * `providers/slack/socket.ts` already resolved by choosing a persistent
9
+ * outbound connection). Node's built-in `WebSocket` global (stable since
10
+ * Node 22, this package's minimum) means no dependency is needed here
11
+ * either.
12
+ *
13
+ * Unlike Slack's Socket Mode (an envelope arrives, you ack it — no other
14
+ * protocol state), the Gateway requires real heartbeating: on `HELLO` (op
15
+ * 10) the server names a `heartbeat_interval`, and the client must send a
16
+ * `HEARTBEAT` (op 1) on that cadence or get disconnected. `scheduleHeartbeat`
17
+ * is injected (defaulting to real `setInterval`) specifically so a test can
18
+ * capture the interval and the send callback and invoke it manually —
19
+ * deterministic, no real timers, same spirit as this codebase's injectable
20
+ * clocks elsewhere (`runAgentLoop`'s `now()`) but shaped for a recurring
21
+ * schedule rather than an elapsed-duration check.
22
+ *
23
+ * Session resumption (op 6 `RESUME`) is not implemented — a lost connection
24
+ * reconnects fresh with a new `IDENTIFY`, exactly like Telegram's poll loop
25
+ * and Slack's Socket Mode reconnect both start clean rather than resuming
26
+ * mid-stream. Real, honest, and proportionate: resumption exists to avoid
27
+ * re-fetching guild state after a brief blip, which this adapter (no guild
28
+ * cache) has no use for.
29
+ */
30
+ export interface DiscordGatewayPayload {
31
+ readonly op: number;
32
+ readonly d?: unknown;
33
+ readonly s?: number | null;
34
+ readonly t?: string | null;
35
+ }
36
+ export interface DiscordDispatchEvent {
37
+ readonly type: string;
38
+ readonly data: unknown;
39
+ }
40
+ export interface DiscordGatewayClientConfig {
41
+ readonly token: string;
42
+ readonly intents: number;
43
+ /** Overridable for tests; defaults to the real Discord Gateway. */
44
+ readonly gatewayUrl?: string;
45
+ readonly webSocketImpl?: typeof WebSocket;
46
+ /** Overridable for tests — real default uses `setInterval`/`clearInterval`. */
47
+ readonly scheduleHeartbeat?: (intervalMs: number, send: () => void) => () => void;
48
+ }
49
+ export interface DiscordGatewayClient {
50
+ /** Opens the connection and delivers every dispatch event. Resolves once `READY` (or the first dispatch) confirms the session is live. */
51
+ connect(onDispatch: (event: DiscordDispatchEvent) => Promise<void>): Promise<void>;
52
+ disconnect(): void;
53
+ }
54
+ export declare function createDiscordGatewayClient(config: DiscordGatewayClientConfig): DiscordGatewayClient;
55
+ //# sourceMappingURL=gateway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.d.ts","sourceRoot":"","sources":["../../../src/providers/discord/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AASH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,CAAA;IACpB,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,mEAAmE;IACnE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,SAAS,CAAA;IACzC,+EAA+E;IAC/E,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAClF;AAED,MAAM,WAAW,oBAAoB;IACnC,0IAA0I;IAC1I,OAAO,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClF,UAAU,IAAI,IAAI,CAAA;CACnB;AASD,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,0BAA0B,GACjC,oBAAoB,CAsFtB"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Discord's real-time Gateway — a persistent WebSocket, the only real
3
+ * mechanism for a bot with no public HTTPS endpoint to receive messages and
4
+ * interactions (unlike Telegram, Discord has no polling REST alternative;
5
+ * the alternative, an "Interactions Endpoint URL" webhook, needs a public
6
+ * HTTPS endpoint plus Ed25519 signature verification — the same "no public
7
+ * plane deployed" problem `providers/telegram/adapter.ts` and
8
+ * `providers/slack/socket.ts` already resolved by choosing a persistent
9
+ * outbound connection). Node's built-in `WebSocket` global (stable since
10
+ * Node 22, this package's minimum) means no dependency is needed here
11
+ * either.
12
+ *
13
+ * Unlike Slack's Socket Mode (an envelope arrives, you ack it — no other
14
+ * protocol state), the Gateway requires real heartbeating: on `HELLO` (op
15
+ * 10) the server names a `heartbeat_interval`, and the client must send a
16
+ * `HEARTBEAT` (op 1) on that cadence or get disconnected. `scheduleHeartbeat`
17
+ * is injected (defaulting to real `setInterval`) specifically so a test can
18
+ * capture the interval and the send callback and invoke it manually —
19
+ * deterministic, no real timers, same spirit as this codebase's injectable
20
+ * clocks elsewhere (`runAgentLoop`'s `now()`) but shaped for a recurring
21
+ * schedule rather than an elapsed-duration check.
22
+ *
23
+ * Session resumption (op 6 `RESUME`) is not implemented — a lost connection
24
+ * reconnects fresh with a new `IDENTIFY`, exactly like Telegram's poll loop
25
+ * and Slack's Socket Mode reconnect both start clean rather than resuming
26
+ * mid-stream. Real, honest, and proportionate: resumption exists to avoid
27
+ * re-fetching guild state after a brief blip, which this adapter (no guild
28
+ * cache) has no use for.
29
+ */
30
+ const OP_DISPATCH = 0;
31
+ const OP_HEARTBEAT = 1;
32
+ const OP_IDENTIFY = 2;
33
+ const OP_RECONNECT = 7;
34
+ const OP_INVALID_SESSION = 9;
35
+ const OP_HELLO = 10;
36
+ const DEFAULT_GATEWAY_URL = 'wss://gateway.discord.gg/?v=10&encoding=json';
37
+ function defaultScheduleHeartbeat(intervalMs, send) {
38
+ const id = setInterval(send, intervalMs);
39
+ return () => clearInterval(id);
40
+ }
41
+ export function createDiscordGatewayClient(config) {
42
+ const gatewayUrl = config.gatewayUrl ?? DEFAULT_GATEWAY_URL;
43
+ const WebSocketImpl = config.webSocketImpl ?? WebSocket;
44
+ const scheduleHeartbeat = config.scheduleHeartbeat ?? defaultScheduleHeartbeat;
45
+ let socket;
46
+ let stopHeartbeat;
47
+ let lastSequence = null;
48
+ let connected = false;
49
+ function send(payload) {
50
+ socket?.send(JSON.stringify(payload));
51
+ }
52
+ function identify() {
53
+ send({
54
+ op: OP_IDENTIFY,
55
+ d: {
56
+ token: config.token,
57
+ intents: config.intents,
58
+ properties: { os: 'linux', browser: 'cogenta', device: 'cogenta' },
59
+ },
60
+ });
61
+ }
62
+ async function openSocket(onDispatch) {
63
+ const ws = new WebSocketImpl(gatewayUrl);
64
+ socket = ws;
65
+ await new Promise((resolve, reject) => {
66
+ ws.addEventListener('open', () => resolve());
67
+ ws.addEventListener('error', () => reject(new Error('Discord Gateway connection failed to open.')));
68
+ });
69
+ ws.addEventListener('close', () => {
70
+ stopHeartbeat?.();
71
+ stopHeartbeat = undefined;
72
+ if (connected)
73
+ void openSocket(onDispatch);
74
+ });
75
+ ws.addEventListener('message', (event) => {
76
+ const raw = typeof event.data === 'string' ? event.data : String(event.data);
77
+ const payload = JSON.parse(raw);
78
+ if (payload.s !== undefined && payload.s !== null)
79
+ lastSequence = payload.s;
80
+ void (async () => {
81
+ if (payload.op === OP_HELLO) {
82
+ const hello = payload.d;
83
+ stopHeartbeat?.();
84
+ stopHeartbeat = scheduleHeartbeat(hello.heartbeat_interval, () => {
85
+ send({ op: OP_HEARTBEAT, d: lastSequence });
86
+ });
87
+ identify();
88
+ return;
89
+ }
90
+ if (payload.op === OP_RECONNECT || payload.op === OP_INVALID_SESSION) {
91
+ ws.close();
92
+ return;
93
+ }
94
+ if (payload.op === OP_DISPATCH && payload.t !== null && payload.t !== undefined) {
95
+ await onDispatch({ type: payload.t, data: payload.d });
96
+ }
97
+ })();
98
+ });
99
+ }
100
+ return {
101
+ async connect(onDispatch) {
102
+ connected = true;
103
+ await openSocket(onDispatch);
104
+ },
105
+ disconnect() {
106
+ connected = false;
107
+ stopHeartbeat?.();
108
+ stopHeartbeat = undefined;
109
+ socket?.close();
110
+ socket = undefined;
111
+ },
112
+ };
113
+ }
114
+ //# sourceMappingURL=gateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../../src/providers/discord/gateway.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { InboundHandler } from '../../adapter.js';
2
+ import type { CommandRouter } from '../../inbound/router.js';
3
+ import type { ChannelLinkStore } from '../../linking/store.js';
4
+ import type { DiscordClient } from './client.js';
5
+ import type { DiscordDispatchEvent } from './gateway.js';
6
+ export interface DiscordInboundDeps {
7
+ readonly client: DiscordClient;
8
+ readonly linkStore: ChannelLinkStore;
9
+ readonly router: CommandRouter;
10
+ /** The handler registered via the adapter's `onInbound()`, if any. */
11
+ getExternalHandler(): InboundHandler | undefined;
12
+ }
13
+ /**
14
+ * The real routing/authorization wiring for one Discord dispatch event —
15
+ * extracted from the adapter, same shape as `providers/slack/inbound.ts`.
16
+ * Handles both a typed message (`MESSAGE_CREATE`) and a button press
17
+ * (`INTERACTION_CREATE` with `type: MESSAGE_COMPONENT`, whose `custom_id` is
18
+ * itself a command string rendered by `render.ts`) through the exact same
19
+ * `CommandRouter.route()`, never a second, parallel authorization path.
20
+ */
21
+ export declare function createDiscordInboundHandler(deps: DiscordInboundDeps): (event: DiscordDispatchEvent) => Promise<void>;
22
+ //# sourceMappingURL=inbound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../../../src/providers/discord/inbound.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAqBxD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,sEAAsE;IACtE,kBAAkB,IAAI,cAAc,GAAG,SAAS,CAAA;CACjD;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,kBAAkB,GACvB,CAAC,KAAK,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CA+DhD"}