@open-mercato/channel-discord 0.6.8-develop.7090.1.4dbca7b350

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 (220) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/build.mjs +7 -0
  3. package/dist/index.js +5 -0
  4. package/dist/index.js.map +7 -0
  5. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-001.spec.js +86 -0
  6. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-001.spec.js.map +7 -0
  7. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-002.spec.js +39 -0
  8. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-002.spec.js.map +7 -0
  9. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-003.spec.js +97 -0
  10. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-003.spec.js.map +7 -0
  11. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-004.spec.js +65 -0
  12. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-004.spec.js.map +7 -0
  13. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-005.spec.js +110 -0
  14. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-005.spec.js.map +7 -0
  15. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-006.spec.js +103 -0
  16. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-006.spec.js.map +7 -0
  17. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-007.spec.js +93 -0
  18. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-007.spec.js.map +7 -0
  19. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-008.spec.js +52 -0
  20. package/dist/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-008.spec.js.map +7 -0
  21. package/dist/modules/channel_discord/__integration__/helpers/discordSignature.js +46 -0
  22. package/dist/modules/channel_discord/__integration__/helpers/discordSignature.js.map +7 -0
  23. package/dist/modules/channel_discord/__integration__/helpers/eventsQueue.js +16 -0
  24. package/dist/modules/channel_discord/__integration__/helpers/eventsQueue.js.map +7 -0
  25. package/dist/modules/channel_discord/__integration__/meta.js +10 -0
  26. package/dist/modules/channel_discord/__integration__/meta.js.map +7 -0
  27. package/dist/modules/channel_discord/acl.js +15 -0
  28. package/dist/modules/channel_discord/acl.js.map +7 -0
  29. package/dist/modules/channel_discord/ai-agents.js +97 -0
  30. package/dist/modules/channel_discord/ai-agents.js.map +7 -0
  31. package/dist/modules/channel_discord/api/ai-auto-reply/channels/route.js +73 -0
  32. package/dist/modules/channel_discord/api/ai-auto-reply/channels/route.js.map +7 -0
  33. package/dist/modules/channel_discord/api/channels/[id]/ai-auto-reply/route.js +222 -0
  34. package/dist/modules/channel_discord/api/channels/[id]/ai-auto-reply/route.js.map +7 -0
  35. package/dist/modules/channel_discord/api/interactions/route.js +173 -0
  36. package/dist/modules/channel_discord/api/interactions/route.js.map +7 -0
  37. package/dist/modules/channel_discord/backend/channel_discord/channels/[id]/ai-auto-reply/page.js +196 -0
  38. package/dist/modules/channel_discord/backend/channel_discord/channels/[id]/ai-auto-reply/page.js.map +7 -0
  39. package/dist/modules/channel_discord/backend/channel_discord/channels/[id]/ai-auto-reply/page.meta.js +14 -0
  40. package/dist/modules/channel_discord/backend/channel_discord/channels/[id]/ai-auto-reply/page.meta.js.map +7 -0
  41. package/dist/modules/channel_discord/cli.js +142 -0
  42. package/dist/modules/channel_discord/cli.js.map +7 -0
  43. package/dist/modules/channel_discord/commands/ai-reply-proposal.js +116 -0
  44. package/dist/modules/channel_discord/commands/ai-reply-proposal.js.map +7 -0
  45. package/dist/modules/channel_discord/commands/update-ai-auto-reply.js +104 -0
  46. package/dist/modules/channel_discord/commands/update-ai-auto-reply.js.map +7 -0
  47. package/dist/modules/channel_discord/di.js +23 -0
  48. package/dist/modules/channel_discord/di.js.map +7 -0
  49. package/dist/modules/channel_discord/index.js +9 -0
  50. package/dist/modules/channel_discord/index.js.map +7 -0
  51. package/dist/modules/channel_discord/integration.js +96 -0
  52. package/dist/modules/channel_discord/integration.js.map +7 -0
  53. package/dist/modules/channel_discord/lib/adapter.js +248 -0
  54. package/dist/modules/channel_discord/lib/adapter.js.map +7 -0
  55. package/dist/modules/channel_discord/lib/ai-agent-directory.js +45 -0
  56. package/dist/modules/channel_discord/lib/ai-agent-directory.js.map +7 -0
  57. package/dist/modules/channel_discord/lib/ai-features.js +9 -0
  58. package/dist/modules/channel_discord/lib/ai-features.js.map +7 -0
  59. package/dist/modules/channel_discord/lib/ai-proposal-contract.js +9 -0
  60. package/dist/modules/channel_discord/lib/ai-proposal-contract.js.map +7 -0
  61. package/dist/modules/channel_discord/lib/ai-proposal.js +109 -0
  62. package/dist/modules/channel_discord/lib/ai-proposal.js.map +7 -0
  63. package/dist/modules/channel_discord/lib/ai-reply.js +96 -0
  64. package/dist/modules/channel_discord/lib/ai-reply.js.map +7 -0
  65. package/dist/modules/channel_discord/lib/ai-service-principal.js +70 -0
  66. package/dist/modules/channel_discord/lib/ai-service-principal.js.map +7 -0
  67. package/dist/modules/channel_discord/lib/capabilities.js +42 -0
  68. package/dist/modules/channel_discord/lib/capabilities.js.map +7 -0
  69. package/dist/modules/channel_discord/lib/channel-access.js +62 -0
  70. package/dist/modules/channel_discord/lib/channel-access.js.map +7 -0
  71. package/dist/modules/channel_discord/lib/channel-state-store.js +103 -0
  72. package/dist/modules/channel_discord/lib/channel-state-store.js.map +7 -0
  73. package/dist/modules/channel_discord/lib/convert-outbound.js +38 -0
  74. package/dist/modules/channel_discord/lib/convert-outbound.js.map +7 -0
  75. package/dist/modules/channel_discord/lib/credentials.js +61 -0
  76. package/dist/modules/channel_discord/lib/credentials.js.map +7 -0
  77. package/dist/modules/channel_discord/lib/discord-gateway-client.js +289 -0
  78. package/dist/modules/channel_discord/lib/discord-gateway-client.js.map +7 -0
  79. package/dist/modules/channel_discord/lib/discord-rest.js +203 -0
  80. package/dist/modules/channel_discord/lib/discord-rest.js.map +7 -0
  81. package/dist/modules/channel_discord/lib/failure-reason.js +30 -0
  82. package/dist/modules/channel_discord/lib/failure-reason.js.map +7 -0
  83. package/dist/modules/channel_discord/lib/gateway-bridge.js +42 -0
  84. package/dist/modules/channel_discord/lib/gateway-bridge.js.map +7 -0
  85. package/dist/modules/channel_discord/lib/health.js +37 -0
  86. package/dist/modules/channel_discord/lib/health.js.map +7 -0
  87. package/dist/modules/channel_discord/lib/interactions-dispatch.js +164 -0
  88. package/dist/modules/channel_discord/lib/interactions-dispatch.js.map +7 -0
  89. package/dist/modules/channel_discord/lib/interactions-handler.js +115 -0
  90. package/dist/modules/channel_discord/lib/interactions-handler.js.map +7 -0
  91. package/dist/modules/channel_discord/lib/interactions-queue.js +17 -0
  92. package/dist/modules/channel_discord/lib/interactions-queue.js.map +7 -0
  93. package/dist/modules/channel_discord/lib/interactions-verify.js +84 -0
  94. package/dist/modules/channel_discord/lib/interactions-verify.js.map +7 -0
  95. package/dist/modules/channel_discord/lib/normalize-inbound.js +59 -0
  96. package/dist/modules/channel_discord/lib/normalize-inbound.js.map +7 -0
  97. package/dist/modules/channel_discord/lib/preset.js +63 -0
  98. package/dist/modules/channel_discord/lib/preset.js.map +7 -0
  99. package/dist/modules/channel_discord/lib/queue-strategy.js +12 -0
  100. package/dist/modules/channel_discord/lib/queue-strategy.js.map +7 -0
  101. package/dist/modules/channel_discord/lib/slash-commands.js +42 -0
  102. package/dist/modules/channel_discord/lib/slash-commands.js.map +7 -0
  103. package/dist/modules/channel_discord/message-types.js +64 -0
  104. package/dist/modules/channel_discord/message-types.js.map +7 -0
  105. package/dist/modules/channel_discord/setup.js +62 -0
  106. package/dist/modules/channel_discord/setup.js.map +7 -0
  107. package/dist/modules/channel_discord/subscribers/ai-auto-reply.js +185 -0
  108. package/dist/modules/channel_discord/subscribers/ai-auto-reply.js.map +7 -0
  109. package/dist/modules/channel_discord/widgets/injection/ai-auto-reply/widget.client.js +79 -0
  110. package/dist/modules/channel_discord/widgets/injection/ai-auto-reply/widget.client.js.map +7 -0
  111. package/dist/modules/channel_discord/widgets/injection/ai-auto-reply/widget.js +20 -0
  112. package/dist/modules/channel_discord/widgets/injection/ai-auto-reply/widget.js.map +7 -0
  113. package/dist/modules/channel_discord/widgets/injection/connect/widget.client.js +220 -0
  114. package/dist/modules/channel_discord/widgets/injection/connect/widget.client.js.map +7 -0
  115. package/dist/modules/channel_discord/widgets/injection/connect/widget.js +17 -0
  116. package/dist/modules/channel_discord/widgets/injection/connect/widget.js.map +7 -0
  117. package/dist/modules/channel_discord/widgets/injection-table.js +26 -0
  118. package/dist/modules/channel_discord/widgets/injection-table.js.map +7 -0
  119. package/dist/modules/channel_discord/workers/discord-gateway.js +270 -0
  120. package/dist/modules/channel_discord/workers/discord-gateway.js.map +7 -0
  121. package/dist/modules/channel_discord/workers/discord-interactions.js +113 -0
  122. package/dist/modules/channel_discord/workers/discord-interactions.js.map +7 -0
  123. package/jest.config.cjs +38 -0
  124. package/package.json +104 -0
  125. package/src/index.ts +1 -0
  126. package/src/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-001.spec.ts +109 -0
  127. package/src/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-002.spec.ts +58 -0
  128. package/src/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-003.spec.ts +175 -0
  129. package/src/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-004.spec.ts +83 -0
  130. package/src/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-005.spec.ts +156 -0
  131. package/src/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-006.spec.ts +144 -0
  132. package/src/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-007.spec.ts +119 -0
  133. package/src/modules/channel_discord/__integration__/TC-CHANNEL-DISCORD-008.spec.ts +75 -0
  134. package/src/modules/channel_discord/__integration__/helpers/discordSignature.ts +77 -0
  135. package/src/modules/channel_discord/__integration__/helpers/eventsQueue.ts +26 -0
  136. package/src/modules/channel_discord/__integration__/meta.ts +6 -0
  137. package/src/modules/channel_discord/__tests__/ai-auto-reply.policy.integration.test.ts +286 -0
  138. package/src/modules/channel_discord/acl.ts +19 -0
  139. package/src/modules/channel_discord/ai-agents.ts +157 -0
  140. package/src/modules/channel_discord/api/ai-auto-reply/channels/__tests__/route.test.ts +175 -0
  141. package/src/modules/channel_discord/api/ai-auto-reply/channels/route.ts +99 -0
  142. package/src/modules/channel_discord/api/channels/[id]/ai-auto-reply/__tests__/route.test.ts +311 -0
  143. package/src/modules/channel_discord/api/channels/[id]/ai-auto-reply/route.ts +299 -0
  144. package/src/modules/channel_discord/api/interactions/route.ts +242 -0
  145. package/src/modules/channel_discord/backend/channel_discord/channels/[id]/ai-auto-reply/page.meta.ts +18 -0
  146. package/src/modules/channel_discord/backend/channel_discord/channels/[id]/ai-auto-reply/page.tsx +288 -0
  147. package/src/modules/channel_discord/cli.ts +234 -0
  148. package/src/modules/channel_discord/commands/__tests__/ai-reply-proposal.test.ts +155 -0
  149. package/src/modules/channel_discord/commands/__tests__/update-ai-auto-reply.test.ts +185 -0
  150. package/src/modules/channel_discord/commands/ai-reply-proposal.ts +162 -0
  151. package/src/modules/channel_discord/commands/update-ai-auto-reply.ts +157 -0
  152. package/src/modules/channel_discord/di.ts +26 -0
  153. package/src/modules/channel_discord/i18n/de.json +57 -0
  154. package/src/modules/channel_discord/i18n/en.json +57 -0
  155. package/src/modules/channel_discord/i18n/es.json +57 -0
  156. package/src/modules/channel_discord/i18n/ko.json +57 -0
  157. package/src/modules/channel_discord/i18n/pl.json +57 -0
  158. package/src/modules/channel_discord/index.ts +6 -0
  159. package/src/modules/channel_discord/integration.ts +98 -0
  160. package/src/modules/channel_discord/lib/__tests__/adapter.test.ts +206 -0
  161. package/src/modules/channel_discord/lib/__tests__/ai-agent-directory.test.ts +61 -0
  162. package/src/modules/channel_discord/lib/__tests__/ai-reply.test.ts +115 -0
  163. package/src/modules/channel_discord/lib/__tests__/capabilities.test.ts +170 -0
  164. package/src/modules/channel_discord/lib/__tests__/channel-quarantine.test.ts +89 -0
  165. package/src/modules/channel_discord/lib/__tests__/channel-state-store.auto-reply-marker.test.ts +145 -0
  166. package/src/modules/channel_discord/lib/__tests__/channel-state-store.test.ts +78 -0
  167. package/src/modules/channel_discord/lib/__tests__/convert-outbound.test.ts +39 -0
  168. package/src/modules/channel_discord/lib/__tests__/discord-gateway-client.test.ts +243 -0
  169. package/src/modules/channel_discord/lib/__tests__/failure-reason.test.ts +68 -0
  170. package/src/modules/channel_discord/lib/__tests__/gateway-bridge.test.ts +62 -0
  171. package/src/modules/channel_discord/lib/__tests__/interactions-dispatch.test.ts +223 -0
  172. package/src/modules/channel_discord/lib/__tests__/interactions-handler.test.ts +469 -0
  173. package/src/modules/channel_discord/lib/__tests__/interactions-verify.test.ts +126 -0
  174. package/src/modules/channel_discord/lib/__tests__/normalize-inbound.identity.test.ts +64 -0
  175. package/src/modules/channel_discord/lib/__tests__/normalize-inbound.test.ts +67 -0
  176. package/src/modules/channel_discord/lib/__tests__/preset.test.ts +110 -0
  177. package/src/modules/channel_discord/lib/__tests__/queue-strategy.test.ts +33 -0
  178. package/src/modules/channel_discord/lib/adapter.ts +359 -0
  179. package/src/modules/channel_discord/lib/ai-agent-directory.ts +124 -0
  180. package/src/modules/channel_discord/lib/ai-features.ts +23 -0
  181. package/src/modules/channel_discord/lib/ai-proposal-contract.ts +24 -0
  182. package/src/modules/channel_discord/lib/ai-proposal.ts +179 -0
  183. package/src/modules/channel_discord/lib/ai-reply.ts +179 -0
  184. package/src/modules/channel_discord/lib/ai-service-principal.ts +143 -0
  185. package/src/modules/channel_discord/lib/capabilities.ts +104 -0
  186. package/src/modules/channel_discord/lib/channel-access.ts +110 -0
  187. package/src/modules/channel_discord/lib/channel-state-store.ts +217 -0
  188. package/src/modules/channel_discord/lib/convert-outbound.ts +88 -0
  189. package/src/modules/channel_discord/lib/credentials.ts +88 -0
  190. package/src/modules/channel_discord/lib/discord-gateway-client.ts +432 -0
  191. package/src/modules/channel_discord/lib/discord-rest.ts +393 -0
  192. package/src/modules/channel_discord/lib/failure-reason.ts +49 -0
  193. package/src/modules/channel_discord/lib/gateway-bridge.ts +79 -0
  194. package/src/modules/channel_discord/lib/health.ts +55 -0
  195. package/src/modules/channel_discord/lib/interactions-dispatch.ts +288 -0
  196. package/src/modules/channel_discord/lib/interactions-handler.ts +267 -0
  197. package/src/modules/channel_discord/lib/interactions-queue.ts +52 -0
  198. package/src/modules/channel_discord/lib/interactions-verify.ts +174 -0
  199. package/src/modules/channel_discord/lib/normalize-inbound.ts +89 -0
  200. package/src/modules/channel_discord/lib/preset.ts +109 -0
  201. package/src/modules/channel_discord/lib/queue-strategy.ts +29 -0
  202. package/src/modules/channel_discord/lib/slash-commands.ts +57 -0
  203. package/src/modules/channel_discord/message-types.ts +75 -0
  204. package/src/modules/channel_discord/setup.ts +78 -0
  205. package/src/modules/channel_discord/subscribers/__tests__/ai-auto-reply.armed.test.ts +149 -0
  206. package/src/modules/channel_discord/subscribers/__tests__/ai-auto-reply.failure-marker.test.ts +133 -0
  207. package/src/modules/channel_discord/subscribers/__tests__/ai-auto-reply.test.ts +273 -0
  208. package/src/modules/channel_discord/subscribers/ai-auto-reply.ts +325 -0
  209. package/src/modules/channel_discord/widgets/injection/ai-auto-reply/widget.client.tsx +143 -0
  210. package/src/modules/channel_discord/widgets/injection/ai-auto-reply/widget.ts +19 -0
  211. package/src/modules/channel_discord/widgets/injection/connect/widget.client.tsx +237 -0
  212. package/src/modules/channel_discord/widgets/injection/connect/widget.ts +16 -0
  213. package/src/modules/channel_discord/widgets/injection-table.ts +24 -0
  214. package/src/modules/channel_discord/workers/__tests__/discord-gateway.single-identify.test.ts +88 -0
  215. package/src/modules/channel_discord/workers/__tests__/discord-gateway.test.ts +179 -0
  216. package/src/modules/channel_discord/workers/__tests__/discord-interactions.test.ts +152 -0
  217. package/src/modules/channel_discord/workers/discord-gateway.ts +458 -0
  218. package/src/modules/channel_discord/workers/discord-interactions.ts +162 -0
  219. package/tsconfig.json +9 -0
  220. package/watch.mjs +7 -0
@@ -0,0 +1,62 @@
1
+ import {
2
+ hasChannelAdapter,
3
+ registerChannelAdapter
4
+ } from "@open-mercato/core/modules/communication_channels/lib/adapter-registry-singleton";
5
+ import { createCredentialsService } from "@open-mercato/core/modules/integrations/lib/credentials-service";
6
+ import { createIntegrationLogService } from "@open-mercato/core/modules/integrations/lib/log-service";
7
+ import { createLogger } from "@open-mercato/shared/lib/logger";
8
+ import { getDiscordChannelAdapter } from "./lib/adapter.js";
9
+ import { applyDiscordEnvPreset, CHANNEL_DISCORD_INTEGRATION_ID } from "./lib/preset.js";
10
+ const logger = createLogger("channel_discord").child({ component: "setup" });
11
+ function ensureDiscordAdapterRegistered() {
12
+ if (hasChannelAdapter("discord")) return;
13
+ registerChannelAdapter(getDiscordChannelAdapter());
14
+ }
15
+ ensureDiscordAdapterRegistered();
16
+ const setup = {
17
+ defaultRoleFeatures: {
18
+ // `ai_auto_reply.run` is granted here so a tenant that creates a real
19
+ // channel-bot user and puts it in an admin role gets a principal that can
20
+ // invoke the auto-reply agent. Deployments without such a user do not need
21
+ // the grant at all — the provider's service principal carries it in code
22
+ // (`lib/ai-service-principal.ts`).
23
+ superadmin: [
24
+ "channel_discord.view",
25
+ "channel_discord.configure",
26
+ "channel_discord.ai_auto_reply.run"
27
+ ],
28
+ admin: [
29
+ "channel_discord.view",
30
+ "channel_discord.configure",
31
+ "channel_discord.ai_auto_reply.run"
32
+ ]
33
+ },
34
+ async onTenantCreated({ em, organizationId, tenantId }) {
35
+ ensureDiscordAdapterRegistered();
36
+ const integrationLogService = createIntegrationLogService(em);
37
+ try {
38
+ await applyDiscordEnvPreset({
39
+ credentialsService: createCredentialsService(em),
40
+ integrationLogService,
41
+ scope: { tenantId, organizationId }
42
+ });
43
+ } catch (error) {
44
+ const message = error instanceof Error ? error.message : "Unknown Discord preset error";
45
+ try {
46
+ await integrationLogService.scoped(CHANNEL_DISCORD_INTEGRATION_ID, { tenantId, organizationId }).error(`Failed to apply Discord env preset during tenant setup: ${message}`);
47
+ } catch (logError) {
48
+ const logMessage = logError instanceof Error ? logError.message : "Unknown integration log error";
49
+ logger.error("Failed to apply env preset during tenant setup; persisting to integration logs also failed", {
50
+ presetError: message,
51
+ logError: logMessage
52
+ });
53
+ }
54
+ }
55
+ }
56
+ };
57
+ var setup_default = setup;
58
+ export {
59
+ setup_default as default,
60
+ setup
61
+ };
62
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/modules/channel_discord/setup.ts"],
4
+ "sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\nimport {\n hasChannelAdapter,\n registerChannelAdapter,\n} from '@open-mercato/core/modules/communication_channels/lib/adapter-registry-singleton'\nimport { createCredentialsService } from '@open-mercato/core/modules/integrations/lib/credentials-service'\nimport { createIntegrationLogService } from '@open-mercato/core/modules/integrations/lib/log-service'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { getDiscordChannelAdapter } from './lib/adapter'\nimport { applyDiscordEnvPreset, CHANNEL_DISCORD_INTEGRATION_ID } from './lib/preset'\n\nconst logger = createLogger('channel_discord').child({ component: 'setup' })\n\n/**\n * Register the Discord `ChannelAdapter` exactly once per process at import time.\n * The registry is process-wide, so we guard with `hasChannelAdapter` to silence\n * the duplicate-providerKey error on dev-mode HMR + repeated test imports.\n *\n * Env presets (`OM_CHANNEL_DISCORD_*`, see `lib/preset.ts` and `.env.example`)\n * are optional operator conveniences applied on tenant creation and rerunnable\n * via `yarn mercato channel_discord configure-from-env`; the bot is normally\n * connected via the credential connect flow (`/backend/integrations`). The\n * gateway worker honours `OM_CHANNEL_DISCORD_GATEWAY_DISABLED` to skip the\n * socket in CI / send-only deployments.\n */\nfunction ensureDiscordAdapterRegistered(): void {\n if (hasChannelAdapter('discord')) return\n registerChannelAdapter(getDiscordChannelAdapter())\n}\n\nensureDiscordAdapterRegistered()\n\nexport const setup: ModuleSetupConfig = {\n defaultRoleFeatures: {\n // `ai_auto_reply.run` is granted here so a tenant that creates a real\n // channel-bot user and puts it in an admin role gets a principal that can\n // invoke the auto-reply agent. Deployments without such a user do not need\n // the grant at all \u2014 the provider's service principal carries it in code\n // (`lib/ai-service-principal.ts`).\n superadmin: [\n 'channel_discord.view',\n 'channel_discord.configure',\n 'channel_discord.ai_auto_reply.run',\n ],\n admin: [\n 'channel_discord.view',\n 'channel_discord.configure',\n 'channel_discord.ai_auto_reply.run',\n ],\n },\n async onTenantCreated({ em, organizationId, tenantId }) {\n ensureDiscordAdapterRegistered()\n\n const integrationLogService = createIntegrationLogService(em)\n try {\n await applyDiscordEnvPreset({\n credentialsService: createCredentialsService(em),\n integrationLogService,\n scope: { tenantId, organizationId },\n })\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown Discord preset error'\n try {\n await integrationLogService\n .scoped(CHANNEL_DISCORD_INTEGRATION_ID, { tenantId, organizationId })\n .error(`Failed to apply Discord env preset during tenant setup: ${message}`)\n } catch (logError) {\n const logMessage = logError instanceof Error ? logError.message : 'Unknown integration log error'\n logger.error('Failed to apply env preset during tenant setup; persisting to integration logs also failed', {\n presetError: message,\n logError: logMessage,\n })\n }\n }\n },\n}\n\nexport default setup\n"],
5
+ "mappings": "AACA;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,gCAAgC;AACzC,SAAS,mCAAmC;AAC5C,SAAS,oBAAoB;AAC7B,SAAS,gCAAgC;AACzC,SAAS,uBAAuB,sCAAsC;AAEtE,MAAM,SAAS,aAAa,iBAAiB,EAAE,MAAM,EAAE,WAAW,QAAQ,CAAC;AAc3E,SAAS,iCAAuC;AAC9C,MAAI,kBAAkB,SAAS,EAAG;AAClC,yBAAuB,yBAAyB,CAAC;AACnD;AAEA,+BAA+B;AAExB,MAAM,QAA2B;AAAA,EACtC,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMnB,YAAY;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EACA,MAAM,gBAAgB,EAAE,IAAI,gBAAgB,SAAS,GAAG;AACtD,mCAA+B;AAE/B,UAAM,wBAAwB,4BAA4B,EAAE;AAC5D,QAAI;AACF,YAAM,sBAAsB;AAAA,QAC1B,oBAAoB,yBAAyB,EAAE;AAAA,QAC/C;AAAA,QACA,OAAO,EAAE,UAAU,eAAe;AAAA,MACpC,CAAC;AAAA,IACH,SAAS,OAAO;AACd,YAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,UAAI;AACF,cAAM,sBACH,OAAO,gCAAgC,EAAE,UAAU,eAAe,CAAC,EACnE,MAAM,2DAA2D,OAAO,EAAE;AAAA,MAC/E,SAAS,UAAU;AACjB,cAAM,aAAa,oBAAoB,QAAQ,SAAS,UAAU;AAClE,eAAO,MAAM,8FAA8F;AAAA,UACzG,aAAa;AAAA,UACb,UAAU;AAAA,QACZ,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,185 @@
1
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
2
+ import { createLogger } from "@open-mercato/shared/lib/logger";
3
+ import { CommunicationChannel } from "@open-mercato/core/modules/communication_channels/data/entities";
4
+ import { Message } from "@open-mercato/core/modules/messages/data/entities";
5
+ import { resolveCommunicationChannelsSystemUserId } from "@open-mercato/core/modules/communication_channels/lib/system-user";
6
+ import {
7
+ CHANNEL_DISCORD_AUTO_REPLY_AGENT_ID,
8
+ discordAutoReplyOutputSchema
9
+ } from "../ai-agents.js";
10
+ import {
11
+ classifyDiscordMessage,
12
+ hasStoredAutoReplyFailure,
13
+ isAiAssistantAvailable,
14
+ isAiAutoReplyEnabled,
15
+ resolveAiAgentId
16
+ } from "../lib/ai-reply.js";
17
+ import { fileDiscordReplyProposal } from "../lib/ai-proposal.js";
18
+ import { resolveDiscordAiPrincipal } from "../lib/ai-service-principal.js";
19
+ import { recordDiscordAutoReplyOutcome } from "../lib/channel-state-store.js";
20
+ import { describeAgentFailure } from "../lib/failure-reason.js";
21
+ const logger = createLogger("channel_discord").child({ component: "ai-auto-reply" });
22
+ const metadata = {
23
+ event: "communication_channels.message.received",
24
+ persistent: true,
25
+ id: "channel_discord:ai-auto-reply"
26
+ };
27
+ const AUTO_SEND_MIN_CONFIDENCE = 0.6;
28
+ function resolveFromCtx(ctx, name) {
29
+ if (typeof ctx?.resolve === "function") return ctx.resolve(name);
30
+ if (ctx?.container && typeof ctx.container.resolve === "function") return ctx.container.resolve(name);
31
+ throw new Error(`[internal] channel_discord ai-auto-reply: no resolver for '${name}'`);
32
+ }
33
+ async function handler(payload, ctx) {
34
+ if (payload?.providerKey !== "discord") return;
35
+ if (payload?.direction && payload.direction !== "inbound") return;
36
+ if (!payload?.messageId || !payload?.channelId || !payload?.tenantId) return;
37
+ const em = resolveFromCtx(ctx, "em").fork();
38
+ const scope = { tenantId: payload.tenantId, organizationId: payload.organizationId ?? null };
39
+ const channel = await findOneWithDecryption(
40
+ em,
41
+ CommunicationChannel,
42
+ { id: payload.channelId, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
43
+ void 0,
44
+ scope
45
+ );
46
+ if (!channel) return;
47
+ if (!isAiAutoReplyEnabled(channel.channelState)) return;
48
+ if (!isAiAssistantAvailable(ctx)) {
49
+ logger.debug("ai_assistant peer unavailable \u2014 skipping auto-reply (channel still works as inbox)");
50
+ return;
51
+ }
52
+ const agentId = resolveAiAgentId(channel.channelState) ?? CHANNEL_DISCORD_AUTO_REPLY_AGENT_ID;
53
+ const message = await findOneWithDecryption(
54
+ em,
55
+ Message,
56
+ { id: payload.messageId, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },
57
+ void 0,
58
+ scope
59
+ );
60
+ if (!message) return;
61
+ const body = (message.body ?? "").toString();
62
+ const classification = classifyDiscordMessage(body);
63
+ try {
64
+ await draftAndRoute({
65
+ ctx,
66
+ em,
67
+ message,
68
+ channelId: payload.channelId,
69
+ conversationId: payload.conversationId ?? null,
70
+ agentId,
71
+ body,
72
+ scope,
73
+ tier: classification.tier,
74
+ classificationReason: classification.reason
75
+ });
76
+ if (hasStoredAutoReplyFailure(channel.channelState)) {
77
+ await recordAutoReplyOutcome({ em, channelId: payload.channelId, scope, failure: null });
78
+ }
79
+ } catch (err) {
80
+ logger.warn("discord AI auto-reply failed \u2014 degrading to no-op", { channelId: payload.channelId, err });
81
+ await recordAutoReplyOutcome({
82
+ em,
83
+ channelId: payload.channelId,
84
+ scope,
85
+ failure: describeAgentFailure(agentId, err)
86
+ });
87
+ }
88
+ }
89
+ async function recordAutoReplyOutcome(params) {
90
+ try {
91
+ await recordDiscordAutoReplyOutcome(params);
92
+ } catch (err) {
93
+ logger.warn("failed to persist the discord auto-reply outcome marker", {
94
+ channelId: params.channelId,
95
+ err
96
+ });
97
+ }
98
+ }
99
+ async function draftAndRoute(args) {
100
+ const { ctx, em, message, channelId, conversationId, agentId, body, scope, tier, classificationReason } = args;
101
+ const principal = await resolveDiscordAiPrincipal({ em, resolver: { resolve: (name) => resolveFromCtx(ctx, name) }, scope });
102
+ const draft = await runAutoReplyAgent({ ctx, agentId, body, principal, sessionId: message.threadId ?? message.id });
103
+ if (!draft) return;
104
+ const reply = draft.reply.trim();
105
+ if (!reply) return;
106
+ const autoSend = tier === "easy" && !draft.requiresHuman && draft.confidence >= AUTO_SEND_MIN_CONFIDENCE;
107
+ const commandBus = resolveFromCtx(ctx, "commandBus");
108
+ const containerCtx = {
109
+ container: { resolve: (name) => resolveFromCtx(ctx, name) },
110
+ auth: null,
111
+ organizationScope: null,
112
+ selectedOrganizationId: scope.organizationId,
113
+ organizationIds: scope.organizationId ? [scope.organizationId] : null
114
+ };
115
+ const botUserId = await resolveCommunicationChannelsSystemUserId(em, scope.tenantId, null);
116
+ if (!autoSend) {
117
+ const reason = draft.requiresHuman ? "model-requested-review" : draft.confidence < AUTO_SEND_MIN_CONFIDENCE ? `low-confidence:${draft.confidence.toFixed(2)}` : classificationReason;
118
+ await fileDiscordReplyProposal({
119
+ em,
120
+ commandBus,
121
+ containerCtx,
122
+ scope,
123
+ channelId,
124
+ inboundMessage: message,
125
+ externalConversationId: conversationId,
126
+ botUserId,
127
+ proposedReply: reply,
128
+ summary: draft.summary,
129
+ reason
130
+ });
131
+ return;
132
+ }
133
+ await commandBus.execute("messages.messages.compose", {
134
+ input: {
135
+ type: "channel.discord",
136
+ visibility: "public",
137
+ subject: (message.subject ?? "Discord reply").toString().slice(0, 200) || "Discord reply",
138
+ body: reply,
139
+ bodyFormat: "markdown",
140
+ priority: "normal",
141
+ sendViaEmail: false,
142
+ parentMessageId: message.threadId ?? message.id,
143
+ isDraft: false,
144
+ tenantId: scope.tenantId,
145
+ organizationId: scope.organizationId,
146
+ userId: botUserId
147
+ },
148
+ ctx: containerCtx
149
+ });
150
+ }
151
+ async function runAutoReplyAgent(args) {
152
+ const { agentId, body, principal, sessionId } = args;
153
+ const mod = await import("@open-mercato/ai-assistant");
154
+ if (typeof mod.runAiAgentObject !== "function") return null;
155
+ const container = { resolve: (name) => resolveFromCtx(args.ctx, name) };
156
+ const result = await mod.runAiAgentObject({
157
+ agentId,
158
+ input: body,
159
+ authContext: {
160
+ tenantId: principal.tenantId,
161
+ organizationId: principal.organizationId,
162
+ userId: principal.userId,
163
+ features: principal.features,
164
+ isSuperAdmin: principal.isSuperAdmin
165
+ },
166
+ container,
167
+ // Preserve multi-turn context per conversation thread.
168
+ sessionId
169
+ });
170
+ const parsed = discordAutoReplyOutputSchema.safeParse(result.object);
171
+ if (!parsed.success) {
172
+ logger.warn("auto-reply agent returned an object that does not match the declared schema", {
173
+ agentId,
174
+ issues: parsed.error.issues.map((issue) => issue.path.join("."))
175
+ });
176
+ return null;
177
+ }
178
+ return parsed.data;
179
+ }
180
+ export {
181
+ AUTO_SEND_MIN_CONFIDENCE,
182
+ handler as default,
183
+ metadata
184
+ };
185
+ //# sourceMappingURL=ai-auto-reply.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/channel_discord/subscribers/ai-auto-reply.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandBus } from '@open-mercato/shared/lib/commands'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { CommunicationChannel } from '@open-mercato/core/modules/communication_channels/data/entities'\nimport { Message } from '@open-mercato/core/modules/messages/data/entities'\nimport { resolveCommunicationChannelsSystemUserId } from '@open-mercato/core/modules/communication_channels/lib/system-user'\nimport {\n CHANNEL_DISCORD_AUTO_REPLY_AGENT_ID,\n discordAutoReplyOutputSchema,\n type DiscordAutoReplyOutput,\n} from '../ai-agents'\nimport {\n classifyDiscordMessage,\n hasStoredAutoReplyFailure,\n isAiAssistantAvailable,\n isAiAutoReplyEnabled,\n resolveAiAgentId,\n type SubscriberResolver,\n} from '../lib/ai-reply'\nimport { fileDiscordReplyProposal } from '../lib/ai-proposal'\nimport { resolveDiscordAiPrincipal, type DiscordAiPrincipal } from '../lib/ai-service-principal'\nimport { recordDiscordAutoReplyOutcome } from '../lib/channel-state-store'\nimport { describeAgentFailure } from '../lib/failure-reason'\n\nconst logger = createLogger('channel_discord').child({ component: 'ai-auto-reply' })\n\n/**\n * AI auto-reply subscriber (SPEC 2026-06-19 \u00A7 AI bot wiring, issue #4778).\n *\n * Listens to `communication_channels.message.received`, filters to Discord, and \u2014\n * only when the channel opted in (default OFF) AND the optional `ai_assistant`\n * peer is present \u2014 drafts a reply through the programmatic agent runtime. An\n * \"easy\" message the model is confident about is answered directly through the\n * generic hub outbound path (compose \u2192 outbound-bridge \u2192 `deliver_outbound` \u2192\n * Discord REST). Anything else becomes a proposal a human approves. Nothing\n * Discord-specific leaks into the send path; any module could do the same.\n *\n * How the guarantees actually hold, rather than by convention:\n *\n * - `ai_assistant` is an OPTIONAL peer resolved softly; absent \u2192 no-op, and the\n * channel keeps working as a plain inbox.\n * - The agent runs under a real, tenant-scoped identity with real `features`\n * (`lib/ai-service-principal.ts`), never `features: []` and never as a\n * super-admin. `runAiAgentObject` enforces the agent's `requiredFeatures`,\n * `executionMode` and `mutationPolicy` against it; this subscriber cannot\n * widen them.\n * - The agent is object-mode, so the runtime never exposes a tool to the model\n * \u2014 no privileged action is reachable from an inbound Discord message at all,\n * let alone auto-executable.\n * - Auto-send needs THREE independent yeses: the conservative regex tiering\n * says `easy`, the model says it does not need a human, and the model's own\n * confidence clears {@link AUTO_SEND_MIN_CONFIDENCE}. Any single no routes the\n * draft to the approval surface instead.\n * - Every failure degrades to a no-op. A broken model, a denied policy check, a\n * malformed object \u2014 none of them can turn into a send.\n */\nexport const metadata = {\n event: 'communication_channels.message.received',\n persistent: true,\n id: 'channel_discord:ai-auto-reply',\n}\n\n/**\n * Below this, the model's own draft is proposed rather than sent. The number is\n * deliberately high: the cost of a wrong auto-answer in a public Discord server\n * is much higher than the cost of a human glancing at a proposal.\n */\nexport const AUTO_SEND_MIN_CONFIDENCE = 0.6\n\ntype MessageReceivedPayload = {\n messageId?: string\n channelId?: string\n conversationId?: string\n providerKey?: string\n channelType?: string\n direction?: string\n tenantId?: string\n organizationId?: string | null\n}\n\ntype Ctx = SubscriberResolver\n\nfunction resolveFromCtx<T = unknown>(ctx: Ctx, name: string): T {\n if (typeof ctx?.resolve === 'function') return ctx.resolve<T>(name)\n if (ctx?.container && typeof ctx.container.resolve === 'function') return ctx.container.resolve<T>(name)\n throw new Error(`[internal] channel_discord ai-auto-reply: no resolver for '${name}'`)\n}\n\n/** Minimal shape of the optional `@open-mercato/ai-assistant` peer we call. */\ninterface AiAssistantModule {\n runAiAgentObject: (input: {\n agentId: string\n input: string\n authContext: Record<string, unknown>\n container: unknown\n sessionId?: string\n }) => Promise<{ mode: string; object: unknown }>\n}\n\nexport default async function handler(payload: MessageReceivedPayload, ctx: Ctx): Promise<void> {\n // (1) Filter \u2014 Discord inbound only.\n if (payload?.providerKey !== 'discord') return\n if (payload?.direction && payload.direction !== 'inbound') return\n if (!payload?.messageId || !payload?.channelId || !payload?.tenantId) return\n\n const em = resolveFromCtx<EntityManager>(ctx, 'em').fork()\n const scope = { tenantId: payload.tenantId, organizationId: payload.organizationId ?? null }\n\n const channel = await findOneWithDecryption(\n em,\n CommunicationChannel,\n { id: payload.channelId, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },\n undefined,\n scope,\n )\n if (!channel) return\n\n // (2) Per-channel opt-in (default OFF), written by the settings surface at\n // `/backend/channel_discord/channels/<id>/ai-auto-reply`.\n if (!isAiAutoReplyEnabled(channel.channelState)) return\n\n // (3) Soft-resolve the optional AI peer \u2014 no-op when absent (module-decoupling).\n if (!isAiAssistantAvailable(ctx)) {\n logger.debug('ai_assistant peer unavailable \u2014 skipping auto-reply (channel still works as inbox)')\n return\n }\n\n // The settings form always stores an explicit agent id; the fallback covers a\n // channel armed by an older preset or by hand.\n const agentId = resolveAiAgentId(channel.channelState) ?? CHANNEL_DISCORD_AUTO_REPLY_AGENT_ID\n\n const message = await findOneWithDecryption(\n em,\n Message,\n { id: payload.messageId, tenantId: scope.tenantId, organizationId: scope.organizationId, deletedAt: null },\n undefined,\n scope,\n )\n if (!message) return\n const body = (message.body ?? '').toString()\n\n // (4) Classify \u2014 easy vs complex. Conservative by construction: it can only\n // ever escalate a message away from auto-send.\n const classification = classifyDiscordMessage(body)\n\n try {\n await draftAndRoute({\n ctx,\n em,\n message,\n channelId: payload.channelId,\n conversationId: payload.conversationId ?? null,\n agentId,\n body,\n scope,\n tier: classification.tier,\n classificationReason: classification.reason,\n })\n // Clearing costs a read-modify-write, so only pay it on a channel that is\n // actually carrying a stale failure. The row was already loaded above.\n if (hasStoredAutoReplyFailure(channel.channelState)) {\n await recordAutoReplyOutcome({ em, channelId: payload.channelId, scope, failure: null })\n }\n } catch (err) {\n // The no-op is the correct behaviour \u2014 a broken model must never become a\n // send. Staying silent about it is not: the operator's surface would keep\n // reading \"Auto-reply on\" for a channel that answers nothing. Park the reason\n // on the channel so the settings page and the integration panel can show it.\n logger.warn('discord AI auto-reply failed \u2014 degrading to no-op', { channelId: payload.channelId, err })\n await recordAutoReplyOutcome({\n em,\n channelId: payload.channelId,\n scope,\n failure: describeAgentFailure(agentId, err),\n })\n }\n}\n\n/**\n * Persisting the outcome must never be able to turn a degraded no-op into a\n * thrown handler \u2014 the marker is diagnostics, not the job.\n */\nasync function recordAutoReplyOutcome(params: {\n em: EntityManager\n channelId: string\n scope: { tenantId: string; organizationId: string | null }\n failure: string | null\n}): Promise<void> {\n try {\n await recordDiscordAutoReplyOutcome(params)\n } catch (err) {\n logger.warn('failed to persist the discord auto-reply outcome marker', {\n channelId: params.channelId,\n err,\n })\n }\n}\n\nasync function draftAndRoute(args: {\n ctx: Ctx\n em: EntityManager\n message: Message\n channelId: string\n conversationId: string | null\n agentId: string\n body: string\n scope: { tenantId: string; organizationId: string | null }\n tier: 'easy' | 'complex'\n classificationReason: string\n}): Promise<void> {\n const { ctx, em, message, channelId, conversationId, agentId, body, scope, tier, classificationReason } = args\n\n const principal = await resolveDiscordAiPrincipal({ em, resolver: { resolve: (name) => resolveFromCtx(ctx, name) }, scope })\n\n const draft = await runAutoReplyAgent({ ctx, agentId, body, principal, sessionId: message.threadId ?? message.id })\n if (!draft) return\n\n const reply = draft.reply.trim()\n if (!reply) return\n\n const autoSend =\n tier === 'easy' && !draft.requiresHuman && draft.confidence >= AUTO_SEND_MIN_CONFIDENCE\n const commandBus = resolveFromCtx<CommandBus>(ctx, 'commandBus')\n const containerCtx = {\n container: { resolve: (name: string) => resolveFromCtx(ctx, name) },\n auth: null,\n organizationScope: null,\n selectedOrganizationId: scope.organizationId,\n organizationIds: scope.organizationId ? [scope.organizationId] : null,\n }\n const botUserId = await resolveCommunicationChannelsSystemUserId(em, scope.tenantId, null)\n\n if (!autoSend) {\n const reason = draft.requiresHuman\n ? 'model-requested-review'\n : draft.confidence < AUTO_SEND_MIN_CONFIDENCE\n ? `low-confidence:${draft.confidence.toFixed(2)}`\n : classificationReason\n await fileDiscordReplyProposal({\n em,\n commandBus,\n containerCtx,\n scope,\n channelId,\n inboundMessage: message,\n externalConversationId: conversationId,\n botUserId,\n proposedReply: reply,\n summary: draft.summary,\n reason,\n })\n return\n }\n\n // Easy + confident \u2192 answer directly. The hub's outbound-bridge subscriber\n // picks up `messages.message.sent`, resolves the Discord channel via the\n // ChannelThreadMapping, and delivers through `deliver_outbound` \u2192 `sendMessage`.\n // No direct Discord call here \u2014 keep the send path generic and audited.\n await commandBus.execute('messages.messages.compose', {\n input: {\n type: 'channel.discord',\n visibility: 'public' as const,\n subject: (message.subject ?? 'Discord reply').toString().slice(0, 200) || 'Discord reply',\n body: reply,\n bodyFormat: 'markdown' as const,\n priority: 'normal' as const,\n sendViaEmail: false,\n parentMessageId: message.threadId ?? message.id,\n isDraft: false,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n userId: botUserId,\n },\n ctx: containerCtx as never,\n })\n}\n\nasync function runAutoReplyAgent(args: {\n ctx: Ctx\n agentId: string\n body: string\n principal: DiscordAiPrincipal\n sessionId: string\n}): Promise<DiscordAutoReplyOutput | null> {\n const { agentId, body, principal, sessionId } = args\n\n // Dynamic import keeps `ai_assistant` a truly optional peer \u2014 when the package\n // is not installed the import throws and we no-op (already gated by the DI\n // presence check in the handler, so this only runs when the peer is active).\n const mod = (await import('@open-mercato/ai-assistant')) as unknown as AiAssistantModule\n if (typeof mod.runAiAgentObject !== 'function') return null\n\n // The runtime only touches `container.resolve(...)`; a proxy over the\n // subscriber resolver satisfies that without depending on a concrete DI name\n // (mirrors the inbound-processor's `containerProxy` pattern).\n const container = { resolve: (name: string) => resolveFromCtx(args.ctx, name) }\n\n // No `output` override: the agent declares its own schema, so the schema the\n // policy layer validated the agent against is the schema the model is held to.\n const result = await mod.runAiAgentObject({\n agentId,\n input: body,\n authContext: {\n tenantId: principal.tenantId,\n organizationId: principal.organizationId,\n userId: principal.userId,\n features: principal.features,\n isSuperAdmin: principal.isSuperAdmin,\n },\n container,\n // Preserve multi-turn context per conversation thread.\n sessionId,\n })\n\n const parsed = discordAutoReplyOutputSchema.safeParse((result as { object?: unknown }).object)\n if (!parsed.success) {\n logger.warn('auto-reply agent returned an object that does not match the declared schema', {\n agentId,\n issues: parsed.error.issues.map((issue) => issue.path.join('.')),\n })\n return null\n }\n return parsed.data\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,79 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
7
+ import { Button } from "@open-mercato/ui/primitives/button";
8
+ import { Tag } from "@open-mercato/ui/primitives/tag";
9
+ import { ErrorMessage, LoadingMessage } from "@open-mercato/ui/backend/detail";
10
+ function DiscordAiAutoReplyWidget(_props) {
11
+ const t = useT();
12
+ const [entries, setEntries] = React.useState(null);
13
+ const [truncated, setTruncated] = React.useState(false);
14
+ const [errorMessage, setErrorMessage] = React.useState(null);
15
+ React.useEffect(() => {
16
+ let cancelled = false;
17
+ async function load() {
18
+ const listed = await apiCall(
19
+ "/api/channel_discord/ai-auto-reply/channels"
20
+ ).catch(() => null);
21
+ if (cancelled) return;
22
+ if (!listed?.ok) {
23
+ setErrorMessage(
24
+ t("channel_discord.aiAutoReply.errors.loadChannels", "Failed to load Discord channels")
25
+ );
26
+ setEntries([]);
27
+ return;
28
+ }
29
+ setEntries(listed.result?.items ?? []);
30
+ setTruncated(Boolean(listed.result?.truncated));
31
+ }
32
+ void load();
33
+ return () => {
34
+ cancelled = true;
35
+ };
36
+ }, [t]);
37
+ if (errorMessage) return /* @__PURE__ */ jsx(ErrorMessage, { label: errorMessage });
38
+ if (entries === null) {
39
+ return /* @__PURE__ */ jsx(LoadingMessage, { label: t("channel_discord.aiAutoReply.loadingChannels", "Loading Discord channels...") });
40
+ }
41
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
42
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t(
43
+ "channel_discord.aiAutoReply.widget.description",
44
+ "Let an AI agent answer inbound Discord messages. Every channel is off by default, and anything sensitive or low-confidence is proposed for a human to approve instead of being sent."
45
+ ) }),
46
+ entries.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t(
47
+ "channel_discord.aiAutoReply.widget.noChannels",
48
+ "Connect a Discord bot first \u2014 auto-reply is configured per channel."
49
+ ) }) : /* @__PURE__ */ jsx("ul", { className: "divide-y rounded-md border", children: entries.map((entry) => /* @__PURE__ */ jsxs("li", { className: "flex flex-wrap items-center justify-between gap-3 p-3", children: [
50
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
51
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: entry.displayName }),
52
+ /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-muted-foreground", children: entry.aiAgentId ?? t("channel_discord.aiAutoReply.widget.noAgent", "No agent selected") }),
53
+ entry.aiAutoReplyLastError ? /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-status-error-text", children: t("channel_discord.aiAutoReply.widget.lastError", "Last attempt failed: {reason}", {
54
+ reason: entry.aiAutoReplyLastError
55
+ }) }) : null
56
+ ] }),
57
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
58
+ entry.aiAutoReplyEnabled ? (
59
+ // An armed channel whose last attempt failed is not "on" in any
60
+ // sense the operator cares about — say so on the tag, not only in
61
+ // the detail line they may not read.
62
+ /* @__PURE__ */ jsx(Tag, { variant: entry.aiAutoReplyLastError ? "error" : "success", dot: true, children: entry.aiAutoReplyLastError ? t("channel_discord.aiAutoReply.widget.failing", "Auto-reply failing") : t("channel_discord.aiAutoReply.widget.on", "Auto-reply on") })
63
+ ) : /* @__PURE__ */ jsx(Tag, { variant: "neutral", children: t("channel_discord.aiAutoReply.widget.off", "Auto-reply off") }),
64
+ /* @__PURE__ */ jsx(Button, { asChild: true, type: "button", variant: "outline", children: /* @__PURE__ */ jsx(Link, { href: `/backend/channel_discord/channels/${encodeURIComponent(entry.channelId)}/ai-auto-reply`, children: t("channel_discord.aiAutoReply.widget.configure", "Configure") }) })
65
+ ] })
66
+ ] }, entry.channelId)) }),
67
+ truncated ? (
68
+ // A capped list that says nothing reads as "these are all your channels".
69
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t(
70
+ "channel_discord.aiAutoReply.widget.truncated",
71
+ "Only the most recent channels are shown. Open a channel from the Channels page to configure the rest."
72
+ ) })
73
+ ) : null
74
+ ] });
75
+ }
76
+ export {
77
+ DiscordAiAutoReplyWidget as default
78
+ };
79
+ //# sourceMappingURL=widget.client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/channel_discord/widgets/injection/ai-auto-reply/widget.client.tsx"],
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\n\ntype ChannelEntry = {\n channelId: string\n displayName: string\n aiAutoReplyEnabled: boolean\n aiAgentId: string | null\n aiAutoReplyLastError: string | null\n aiAutoReplyLastErrorAt: string | null\n}\n\ntype ChannelsResponse = {\n items?: ChannelEntry[]\n truncated?: boolean\n}\n\n/**\n * Entry point to the per-channel AI auto-reply settings, rendered on the Discord\n * integration's detail page (issue #4778).\n *\n * It lives here rather than as a row action on the hub's channel table because\n * injected row actions render on EVERY row of that table \u2014 a \"Discord AI\n * auto-reply\" entry would then appear on Gmail and IMAP channels too. The\n * integration detail spot is Discord-scoped by construction, which is exactly the\n * scoping this affordance needs.\n */\nexport default function DiscordAiAutoReplyWidget(\n _props: InjectionWidgetComponentProps<Record<string, unknown>, Record<string, unknown>>,\n) {\n const t = useT()\n const [entries, setEntries] = React.useState<ChannelEntry[] | null>(null)\n const [truncated, setTruncated] = React.useState(false)\n const [errorMessage, setErrorMessage] = React.useState<string | null>(null)\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n // One call for the whole panel. Listing the channels and then asking the\n // per-channel settings route for each one was a request per channel, and\n // every one of those responses rebuilt the agent registry to render two\n // booleans.\n const listed = await apiCall<ChannelsResponse>(\n '/api/channel_discord/ai-auto-reply/channels',\n ).catch(() => null)\n if (cancelled) return\n if (!listed?.ok) {\n setErrorMessage(\n t('channel_discord.aiAutoReply.errors.loadChannels', 'Failed to load Discord channels'),\n )\n setEntries([])\n return\n }\n setEntries(listed.result?.items ?? [])\n setTruncated(Boolean(listed.result?.truncated))\n }\n void load()\n return () => {\n cancelled = true\n }\n }, [t])\n\n if (errorMessage) return <ErrorMessage label={errorMessage} />\n if (entries === null) {\n return <LoadingMessage label={t('channel_discord.aiAutoReply.loadingChannels', 'Loading Discord channels...')} />\n }\n\n return (\n <div className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'channel_discord.aiAutoReply.widget.description',\n 'Let an AI agent answer inbound Discord messages. Every channel is off by default, and anything sensitive or low-confidence is proposed for a human to approve instead of being sent.',\n )}\n </p>\n {entries.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'channel_discord.aiAutoReply.widget.noChannels',\n 'Connect a Discord bot first \u2014 auto-reply is configured per channel.',\n )}\n </p>\n ) : (\n <ul className=\"divide-y rounded-md border\">\n {entries.map((entry) => (\n <li key={entry.channelId} className=\"flex flex-wrap items-center justify-between gap-3 p-3\">\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm font-medium\">{entry.displayName}</div>\n <div className=\"truncate text-xs text-muted-foreground\">\n {entry.aiAgentId\n ?? t('channel_discord.aiAutoReply.widget.noAgent', 'No agent selected')}\n </div>\n {entry.aiAutoReplyLastError ? (\n <div className=\"truncate text-xs text-status-error-text\">\n {t('channel_discord.aiAutoReply.widget.lastError', 'Last attempt failed: {reason}', {\n reason: entry.aiAutoReplyLastError,\n })}\n </div>\n ) : null}\n </div>\n <div className=\"flex items-center gap-3\">\n {entry.aiAutoReplyEnabled ? (\n // An armed channel whose last attempt failed is not \"on\" in any\n // sense the operator cares about \u2014 say so on the tag, not only in\n // the detail line they may not read.\n <Tag variant={entry.aiAutoReplyLastError ? 'error' : 'success'} dot>\n {entry.aiAutoReplyLastError\n ? t('channel_discord.aiAutoReply.widget.failing', 'Auto-reply failing')\n : t('channel_discord.aiAutoReply.widget.on', 'Auto-reply on')}\n </Tag>\n ) : (\n <Tag variant=\"neutral\">{t('channel_discord.aiAutoReply.widget.off', 'Auto-reply off')}</Tag>\n )}\n <Button asChild type=\"button\" variant=\"outline\">\n <Link href={`/backend/channel_discord/channels/${encodeURIComponent(entry.channelId)}/ai-auto-reply`}>\n {t('channel_discord.aiAutoReply.widget.configure', 'Configure')}\n </Link>\n </Button>\n </div>\n </li>\n ))}\n </ul>\n )}\n {truncated ? (\n // A capped list that says nothing reads as \"these are all your channels\".\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'channel_discord.aiAutoReply.widget.truncated',\n 'Only the most recent channels are shown. Open a channel from the Channels page to configure the rest.',\n )}\n </p>\n ) : null}\n </div>\n )\n}\n"],
5
+ "mappings": ";AAsE2B,cAwBb,YAxBa;AApE3B,YAAY,WAAW;AACvB,OAAO,UAAU;AAEjB,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,cAAc;AACvB,SAAS,WAAW;AACpB,SAAS,cAAc,sBAAsB;AA0B9B,SAAR,yBACL,QACA;AACA,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAgC,IAAI;AACxE,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AACtD,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAwB,IAAI;AAE1E,QAAM,UAAU,MAAM;AACpB,QAAI,YAAY;AAChB,mBAAe,OAAO;AAKpB,YAAM,SAAS,MAAM;AAAA,QACnB;AAAA,MACF,EAAE,MAAM,MAAM,IAAI;AAClB,UAAI,UAAW;AACf,UAAI,CAAC,QAAQ,IAAI;AACf;AAAA,UACE,EAAE,mDAAmD,iCAAiC;AAAA,QACxF;AACA,mBAAW,CAAC,CAAC;AACb;AAAA,MACF;AACA,iBAAW,OAAO,QAAQ,SAAS,CAAC,CAAC;AACrC,mBAAa,QAAQ,OAAO,QAAQ,SAAS,CAAC;AAAA,IAChD;AACA,SAAK,KAAK;AACV,WAAO,MAAM;AACX,kBAAY;AAAA,IACd;AAAA,EACF,GAAG,CAAC,CAAC,CAAC;AAEN,MAAI,aAAc,QAAO,oBAAC,gBAAa,OAAO,cAAc;AAC5D,MAAI,YAAY,MAAM;AACpB,WAAO,oBAAC,kBAAe,OAAO,EAAE,+CAA+C,6BAA6B,GAAG;AAAA,EACjH;AAEA,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,OAAE,WAAU,iCACV;AAAA,MACC;AAAA,MACA;AAAA,IACF,GACF;AAAA,IACC,QAAQ,WAAW,IAClB,oBAAC,OAAE,WAAU,iCACV;AAAA,MACC;AAAA,MACA;AAAA,IACF,GACF,IAEA,oBAAC,QAAG,WAAU,8BACX,kBAAQ,IAAI,CAAC,UACZ,qBAAC,QAAyB,WAAU,yDAClC;AAAA,2BAAC,SAAI,WAAU,WACb;AAAA,4BAAC,SAAI,WAAU,gCAAgC,gBAAM,aAAY;AAAA,QACjE,oBAAC,SAAI,WAAU,0CACZ,gBAAM,aACF,EAAE,8CAA8C,mBAAmB,GAC1E;AAAA,QACC,MAAM,uBACL,oBAAC,SAAI,WAAU,2CACZ,YAAE,gDAAgD,iCAAiC;AAAA,UAClF,QAAQ,MAAM;AAAA,QAChB,CAAC,GACH,IACE;AAAA,SACN;AAAA,MACA,qBAAC,SAAI,WAAU,2BACZ;AAAA,cAAM;AAAA;AAAA;AAAA;AAAA,UAIL,oBAAC,OAAI,SAAS,MAAM,uBAAuB,UAAU,WAAW,KAAG,MAChE,gBAAM,uBACH,EAAE,8CAA8C,oBAAoB,IACpE,EAAE,yCAAyC,eAAe,GAChE;AAAA,YAEA,oBAAC,OAAI,SAAQ,WAAW,YAAE,0CAA0C,gBAAgB,GAAE;AAAA,QAExF,oBAAC,UAAO,SAAO,MAAC,MAAK,UAAS,SAAQ,WACpC,8BAAC,QAAK,MAAM,qCAAqC,mBAAmB,MAAM,SAAS,CAAC,kBACjF,YAAE,gDAAgD,WAAW,GAChE,GACF;AAAA,SACF;AAAA,SAjCO,MAAM,SAkCf,CACD,GACH;AAAA,IAED;AAAA;AAAA,MAEC,oBAAC,OAAE,WAAU,iCACV;AAAA,QACC;AAAA,QACA;AAAA,MACF,GACF;AAAA,QACE;AAAA,KACN;AAEJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,20 @@
1
+ import { CHANNEL_DISCORD_VIEW_FEATURE } from "../../../lib/ai-features.js";
2
+ import DiscordAiAutoReplyWidget from "./widget.client.js";
3
+ const widget = {
4
+ metadata: {
5
+ id: "channel_discord.injection.ai-auto-reply",
6
+ title: "AI auto-reply",
7
+ description: "Per-channel AI auto-reply configuration for connected Discord bots.",
8
+ // Viewing the panel needs only read access; the settings page and its PUT
9
+ // route enforce `channel_discord.configure` before anything is written.
10
+ features: [CHANNEL_DISCORD_VIEW_FEATURE],
11
+ priority: 40,
12
+ enabled: true
13
+ },
14
+ Widget: DiscordAiAutoReplyWidget
15
+ };
16
+ var widget_default = widget;
17
+ export {
18
+ widget_default as default
19
+ };
20
+ //# sourceMappingURL=widget.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/channel_discord/widgets/injection/ai-auto-reply/widget.ts"],
4
+ "sourcesContent": ["import type { InjectionWidgetModule } from '@open-mercato/shared/modules/widgets/injection'\nimport { CHANNEL_DISCORD_VIEW_FEATURE } from '../../../lib/ai-features'\nimport DiscordAiAutoReplyWidget from './widget.client'\n\nconst widget: InjectionWidgetModule<Record<string, unknown>, Record<string, unknown>> = {\n metadata: {\n id: 'channel_discord.injection.ai-auto-reply',\n title: 'AI auto-reply',\n description: 'Per-channel AI auto-reply configuration for connected Discord bots.',\n // Viewing the panel needs only read access; the settings page and its PUT\n // route enforce `channel_discord.configure` before anything is written.\n features: [CHANNEL_DISCORD_VIEW_FEATURE],\n priority: 40,\n enabled: true,\n },\n Widget: DiscordAiAutoReplyWidget,\n}\n\nexport default widget\n"],
5
+ "mappings": "AACA,SAAS,oCAAoC;AAC7C,OAAO,8BAA8B;AAErC,MAAM,SAAkF;AAAA,EACtF,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA;AAAA;AAAA,IAGb,UAAU,CAAC,4BAA4B;AAAA,IACvC,UAAU;AAAA,IACV,SAAS;AAAA,EACX;AAAA,EACA,QAAQ;AACV;AAEA,IAAO,iBAAQ;",
6
+ "names": []
7
+ }