@dxos/plugin-discord 0.0.0-stage → 0.14.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 (313) hide show
  1. package/LICENSE +105 -0
  2. package/PLUGIN.mdl +494 -0
  3. package/dist/lib/DiscordAdminOperation.mjs +142 -0
  4. package/dist/lib/DiscordAdminOperation.mjs.map +1 -0
  5. package/dist/lib/DiscordChannel.mjs +95 -0
  6. package/dist/lib/DiscordChannel.mjs.map +1 -0
  7. package/dist/lib/DiscordEvents.mjs +15 -0
  8. package/dist/lib/DiscordEvents.mjs.map +1 -0
  9. package/dist/lib/DiscordOperation.mjs +107 -0
  10. package/dist/lib/DiscordOperation.mjs.map +1 -0
  11. package/dist/lib/DiscordPlugin.mjs +16 -0
  12. package/dist/lib/DiscordPlugin.mjs.map +1 -0
  13. package/dist/lib/DiscordTargetOptions.mjs +30 -0
  14. package/dist/lib/DiscordTargetOptions.mjs.map +1 -0
  15. package/dist/lib/capabilities.browser.mjs +39 -0
  16. package/dist/lib/capabilities.browser.mjs.map +1 -0
  17. package/dist/lib/capabilities.mjs +39 -0
  18. package/dist/lib/capabilities.mjs.map +1 -0
  19. package/dist/lib/capabilities.node.mjs +32 -0
  20. package/dist/lib/capabilities.node.mjs.map +1 -0
  21. package/dist/lib/capabilities.tauri.mjs +39 -0
  22. package/dist/lib/capabilities.tauri.mjs.map +1 -0
  23. package/dist/lib/capabilities.workerd.mjs +32 -0
  24. package/dist/lib/capabilities.workerd.mjs.map +1 -0
  25. package/dist/lib/chunk-DiscordAgentSetup.mjs +266 -0
  26. package/dist/lib/chunk-DiscordAgentSetup.mjs.map +1 -0
  27. package/dist/lib/chunk-DiscordChannelProperties.mjs +95 -0
  28. package/dist/lib/chunk-DiscordChannelProperties.mjs.map +1 -0
  29. package/dist/lib/chunk-PLUGIN.mjs +6 -0
  30. package/dist/lib/chunk-PLUGIN.mjs.map +1 -0
  31. package/dist/lib/chunk-bind-channel.mjs +93 -0
  32. package/dist/lib/chunk-bind-channel.mjs.map +1 -0
  33. package/dist/lib/chunk-bot-gateway.mjs +71 -0
  34. package/dist/lib/chunk-bot-gateway.mjs.map +1 -0
  35. package/dist/lib/chunk-bot.mjs +28 -0
  36. package/dist/lib/chunk-bot.mjs.map +1 -0
  37. package/dist/lib/chunk-channel-backend.mjs +248 -0
  38. package/dist/lib/chunk-channel-backend.mjs.map +1 -0
  39. package/dist/lib/chunk-connector.mjs +150 -0
  40. package/dist/lib/chunk-connector.mjs.map +1 -0
  41. package/dist/lib/chunk-constants.mjs +41 -0
  42. package/dist/lib/chunk-constants.mjs.map +1 -0
  43. package/dist/lib/chunk-crawl.mjs +69 -0
  44. package/dist/lib/chunk-crawl.mjs.map +1 -0
  45. package/dist/lib/chunk-discord-source.mjs +140 -0
  46. package/dist/lib/chunk-discord-source.mjs.map +1 -0
  47. package/dist/lib/chunk-discord.mjs +117 -0
  48. package/dist/lib/chunk-discord.mjs.map +1 -0
  49. package/dist/lib/chunk-errors.mjs +46 -0
  50. package/dist/lib/chunk-errors.mjs.map +1 -0
  51. package/dist/lib/chunk-get-discord-channels.mjs +60 -0
  52. package/dist/lib/chunk-get-discord-channels.mjs.map +1 -0
  53. package/dist/lib/chunk-list-bindings.mjs +13 -0
  54. package/dist/lib/chunk-list-bindings.mjs.map +1 -0
  55. package/dist/lib/chunk-list-bot-guilds.mjs +13 -0
  56. package/dist/lib/chunk-list-bot-guilds.mjs.map +1 -0
  57. package/dist/lib/chunk-list-guild-channels.mjs +13 -0
  58. package/dist/lib/chunk-list-guild-channels.mjs.map +1 -0
  59. package/dist/lib/chunk-list-managed-guilds.mjs +21 -0
  60. package/dist/lib/chunk-list-managed-guilds.mjs.map +1 -0
  61. package/dist/lib/chunk-materialize-target.mjs +47 -0
  62. package/dist/lib/chunk-materialize-target.mjs.map +1 -0
  63. package/dist/lib/chunk-operation-handler.mjs +12 -0
  64. package/dist/lib/chunk-operation-handler.mjs.map +1 -0
  65. package/dist/lib/chunk-react-surface.mjs +29 -0
  66. package/dist/lib/chunk-react-surface.mjs.map +1 -0
  67. package/dist/lib/chunk-rolldown-runtime.mjs +13 -0
  68. package/dist/lib/chunk-schema.mjs +7 -0
  69. package/dist/lib/chunk-schema.mjs.map +1 -0
  70. package/dist/lib/chunk-sync.mjs +184 -0
  71. package/dist/lib/chunk-sync.mjs.map +1 -0
  72. package/dist/lib/chunk-unbind-channel.mjs +40 -0
  73. package/dist/lib/chunk-unbind-channel.mjs.map +1 -0
  74. package/dist/lib/components.mjs +190 -0
  75. package/dist/lib/components.mjs.map +1 -0
  76. package/dist/lib/containers.mjs +8 -0
  77. package/dist/lib/containers.mjs.map +1 -0
  78. package/dist/lib/index.mjs +4 -0
  79. package/dist/lib/meta.mjs +39 -0
  80. package/dist/lib/meta.mjs.map +1 -0
  81. package/dist/lib/ns/DiscordSource.mjs +8 -0
  82. package/dist/lib/ns/DiscordSource.mjs.map +1 -0
  83. package/dist/lib/operations.mjs +20 -0
  84. package/dist/lib/operations.mjs.map +1 -0
  85. package/dist/lib/plugin.mjs +9 -0
  86. package/dist/lib/plugin.mjs.map +1 -0
  87. package/dist/lib/translations.mjs +59 -0
  88. package/dist/lib/translations.mjs.map +1 -0
  89. package/dist/lib/types.mjs +6 -0
  90. package/dist/types/dx.config.d.ts +28 -0
  91. package/dist/types/dx.config.d.ts.map +1 -0
  92. package/dist/types/src/DiscordPlugin.d.ts +37 -0
  93. package/dist/types/src/DiscordPlugin.d.ts.map +1 -0
  94. package/dist/types/src/DiscordSource.d.ts +2 -0
  95. package/dist/types/src/DiscordSource.d.ts.map +1 -0
  96. package/dist/types/src/capabilities/channel-backend.d.ts +19 -0
  97. package/dist/types/src/capabilities/channel-backend.d.ts.map +1 -0
  98. package/dist/types/src/capabilities/channel-backend.test.d.ts +2 -0
  99. package/dist/types/src/capabilities/channel-backend.test.d.ts.map +1 -0
  100. package/dist/types/src/capabilities/connector.d.ts +16 -0
  101. package/dist/types/src/capabilities/connector.d.ts.map +1 -0
  102. package/dist/types/src/capabilities/gen/browser.d.ts +9 -0
  103. package/dist/types/src/capabilities/gen/browser.d.ts.map +1 -0
  104. package/dist/types/src/capabilities/gen/node.d.ts +9 -0
  105. package/dist/types/src/capabilities/gen/node.d.ts.map +1 -0
  106. package/dist/types/src/capabilities/gen/tauri.d.ts +9 -0
  107. package/dist/types/src/capabilities/gen/tauri.d.ts.map +1 -0
  108. package/dist/types/src/capabilities/gen/workerd.d.ts +9 -0
  109. package/dist/types/src/capabilities/gen/workerd.d.ts.map +1 -0
  110. package/dist/types/src/capabilities/index.d.ts +9 -0
  111. package/dist/types/src/capabilities/index.d.ts.map +1 -0
  112. package/dist/types/src/capabilities/operation-handler.d.ts +5 -0
  113. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  114. package/dist/types/src/capabilities/react-surface.d.ts +5 -0
  115. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  116. package/dist/types/src/capabilities/schema.d.ts +4 -0
  117. package/dist/types/src/capabilities/schema.d.ts.map +1 -0
  118. package/dist/types/src/components/DiscordBotStatus/DiscordBotStatus.d.ts +13 -0
  119. package/dist/types/src/components/DiscordBotStatus/DiscordBotStatus.d.ts.map +1 -0
  120. package/dist/types/src/components/DiscordBotStatus/DiscordBotStatus.stories.d.ts +114 -0
  121. package/dist/types/src/components/DiscordBotStatus/DiscordBotStatus.stories.d.ts.map +1 -0
  122. package/dist/types/src/components/DiscordBotStatus/index.d.ts +2 -0
  123. package/dist/types/src/components/DiscordBotStatus/index.d.ts.map +1 -0
  124. package/dist/types/src/components/DiscordBotToolbar/DiscordBotToolbar.d.ts +15 -0
  125. package/dist/types/src/components/DiscordBotToolbar/DiscordBotToolbar.d.ts.map +1 -0
  126. package/dist/types/src/components/DiscordBotToolbar/DiscordBotToolbar.stories.d.ts +113 -0
  127. package/dist/types/src/components/DiscordBotToolbar/DiscordBotToolbar.stories.d.ts.map +1 -0
  128. package/dist/types/src/components/DiscordBotToolbar/index.d.ts +2 -0
  129. package/dist/types/src/components/DiscordBotToolbar/index.d.ts.map +1 -0
  130. package/dist/types/src/components/DiscordChannelForm/DiscordChannelForm.d.ts +18 -0
  131. package/dist/types/src/components/DiscordChannelForm/DiscordChannelForm.d.ts.map +1 -0
  132. package/dist/types/src/components/DiscordChannelForm/DiscordChannelForm.stories.d.ts +112 -0
  133. package/dist/types/src/components/DiscordChannelForm/DiscordChannelForm.stories.d.ts.map +1 -0
  134. package/dist/types/src/components/DiscordChannelForm/index.d.ts +2 -0
  135. package/dist/types/src/components/DiscordChannelForm/index.d.ts.map +1 -0
  136. package/dist/types/src/components/DiscordSetup/DiscordSetup.d.ts +57 -0
  137. package/dist/types/src/components/DiscordSetup/DiscordSetup.d.ts.map +1 -0
  138. package/dist/types/src/components/DiscordSetup/index.d.ts +2 -0
  139. package/dist/types/src/components/DiscordSetup/index.d.ts.map +1 -0
  140. package/dist/types/src/components/index.d.ts +5 -0
  141. package/dist/types/src/components/index.d.ts.map +1 -0
  142. package/dist/types/src/constants.d.ts +45 -0
  143. package/dist/types/src/constants.d.ts.map +1 -0
  144. package/dist/types/src/containers/DiscordAgentSetup/DiscordAgentSetup.d.ts +12 -0
  145. package/dist/types/src/containers/DiscordAgentSetup/DiscordAgentSetup.d.ts.map +1 -0
  146. package/dist/types/src/containers/DiscordAgentSetup/index.d.ts +2 -0
  147. package/dist/types/src/containers/DiscordAgentSetup/index.d.ts.map +1 -0
  148. package/dist/types/src/containers/DiscordChannelProperties/DiscordChannelProperties.d.ts +11 -0
  149. package/dist/types/src/containers/DiscordChannelProperties/DiscordChannelProperties.d.ts.map +1 -0
  150. package/dist/types/src/containers/DiscordChannelProperties/index.d.ts +2 -0
  151. package/dist/types/src/containers/DiscordChannelProperties/index.d.ts.map +1 -0
  152. package/dist/types/src/containers/index.d.ts +4 -0
  153. package/dist/types/src/containers/index.d.ts.map +1 -0
  154. package/dist/types/src/errors.d.ts +131 -0
  155. package/dist/types/src/errors.d.ts.map +1 -0
  156. package/dist/types/src/index.d.ts +4 -0
  157. package/dist/types/src/index.d.ts.map +1 -0
  158. package/dist/types/src/meta.d.ts +33 -0
  159. package/dist/types/src/meta.d.ts.map +1 -0
  160. package/dist/types/src/operations/admin/bind-channel.d.ts +5 -0
  161. package/dist/types/src/operations/admin/bind-channel.d.ts.map +1 -0
  162. package/dist/types/src/operations/admin/bot.d.ts +37 -0
  163. package/dist/types/src/operations/admin/bot.d.ts.map +1 -0
  164. package/dist/types/src/operations/admin/guild-channel.d.ts +11 -0
  165. package/dist/types/src/operations/admin/guild-channel.d.ts.map +1 -0
  166. package/dist/types/src/operations/admin/list-bindings.d.ts +5 -0
  167. package/dist/types/src/operations/admin/list-bindings.d.ts.map +1 -0
  168. package/dist/types/src/operations/admin/list-bot-guilds.d.ts +5 -0
  169. package/dist/types/src/operations/admin/list-bot-guilds.d.ts.map +1 -0
  170. package/dist/types/src/operations/admin/list-guild-channels.d.ts +5 -0
  171. package/dist/types/src/operations/admin/list-guild-channels.d.ts.map +1 -0
  172. package/dist/types/src/operations/admin/list-managed-guilds.d.ts +5 -0
  173. package/dist/types/src/operations/admin/list-managed-guilds.d.ts.map +1 -0
  174. package/dist/types/src/operations/admin/unbind-channel.d.ts +5 -0
  175. package/dist/types/src/operations/admin/unbind-channel.d.ts.map +1 -0
  176. package/dist/types/src/operations/crawl.d.ts +10 -0
  177. package/dist/types/src/operations/crawl.d.ts.map +1 -0
  178. package/dist/types/src/operations/errors.d.ts +33 -0
  179. package/dist/types/src/operations/errors.d.ts.map +1 -0
  180. package/dist/types/src/operations/get-discord-channels.d.ts +16 -0
  181. package/dist/types/src/operations/get-discord-channels.d.ts.map +1 -0
  182. package/dist/types/src/operations/index.d.ts +3 -0
  183. package/dist/types/src/operations/index.d.ts.map +1 -0
  184. package/dist/types/src/operations/materialize-target.d.ts +13 -0
  185. package/dist/types/src/operations/materialize-target.d.ts.map +1 -0
  186. package/dist/types/src/operations/sync.d.ts +32 -0
  187. package/dist/types/src/operations/sync.d.ts.map +1 -0
  188. package/dist/types/src/operations/sync.test.d.ts +2 -0
  189. package/dist/types/src/operations/sync.test.d.ts.map +1 -0
  190. package/dist/types/src/plugin.d.ts +4 -0
  191. package/dist/types/src/plugin.d.ts.map +1 -0
  192. package/dist/types/src/plugin.test.d.ts +2 -0
  193. package/dist/types/src/plugin.test.d.ts.map +1 -0
  194. package/dist/types/src/services/bot-gateway.d.ts +24 -0
  195. package/dist/types/src/services/bot-gateway.d.ts.map +1 -0
  196. package/dist/types/src/services/bot-rest.d.ts +17 -0
  197. package/dist/types/src/services/bot-rest.d.ts.map +1 -0
  198. package/dist/types/src/services/crawl-stores.d.ts +8 -0
  199. package/dist/types/src/services/crawl-stores.d.ts.map +1 -0
  200. package/dist/types/src/services/discord-source.d.ts +28 -0
  201. package/dist/types/src/services/discord-source.d.ts.map +1 -0
  202. package/dist/types/src/services/discord-source.test.d.ts +2 -0
  203. package/dist/types/src/services/discord-source.test.d.ts.map +1 -0
  204. package/dist/types/src/services/discord.d.ts +49 -0
  205. package/dist/types/src/services/discord.d.ts.map +1 -0
  206. package/dist/types/src/services/discord.test.d.ts +2 -0
  207. package/dist/types/src/services/discord.test.d.ts.map +1 -0
  208. package/dist/types/src/services/index.d.ts +5 -0
  209. package/dist/types/src/services/index.d.ts.map +1 -0
  210. package/dist/types/src/services/proxy-http-client.d.ts +38 -0
  211. package/dist/types/src/services/proxy-http-client.d.ts.map +1 -0
  212. package/dist/types/src/testing/crawl-demo.test.d.ts +2 -0
  213. package/dist/types/src/testing/crawl-demo.test.d.ts.map +1 -0
  214. package/dist/types/src/testing/fetch.d.ts +65 -0
  215. package/dist/types/src/testing/fetch.d.ts.map +1 -0
  216. package/dist/types/src/testing/generate-fixtures.d.ts +2 -0
  217. package/dist/types/src/testing/generate-fixtures.d.ts.map +1 -0
  218. package/dist/types/src/testing/index.d.ts +2 -0
  219. package/dist/types/src/testing/index.d.ts.map +1 -0
  220. package/dist/types/src/testing/questions-demo.test.d.ts +2 -0
  221. package/dist/types/src/testing/questions-demo.test.d.ts.map +1 -0
  222. package/dist/types/src/translations.d.ts +98 -0
  223. package/dist/types/src/translations.d.ts.map +1 -0
  224. package/dist/types/src/types/DiscordAdminOperation.d.ts +104 -0
  225. package/dist/types/src/types/DiscordAdminOperation.d.ts.map +1 -0
  226. package/dist/types/src/types/DiscordChannel.d.ts +73 -0
  227. package/dist/types/src/types/DiscordChannel.d.ts.map +1 -0
  228. package/dist/types/src/types/DiscordEvents.d.ts +8 -0
  229. package/dist/types/src/types/DiscordEvents.d.ts.map +1 -0
  230. package/dist/types/src/types/DiscordOperation.d.ts +79 -0
  231. package/dist/types/src/types/DiscordOperation.d.ts.map +1 -0
  232. package/dist/types/src/types/DiscordTargetOptions.d.ts +24 -0
  233. package/dist/types/src/types/DiscordTargetOptions.d.ts.map +1 -0
  234. package/dist/types/src/types/index.d.ts +6 -0
  235. package/dist/types/src/types/index.d.ts.map +1 -0
  236. package/dist/types/tsconfig.tsbuildinfo +1 -0
  237. package/dx.config.ts +34 -0
  238. package/package.json +198 -3
  239. package/src/DiscordPlugin.ts +15 -0
  240. package/src/DiscordSource.ts +7 -0
  241. package/src/__fixtures__/discord-messages.json +216 -0
  242. package/src/capabilities/channel-backend.test.ts +261 -0
  243. package/src/capabilities/channel-backend.ts +185 -0
  244. package/src/capabilities/connector.ts +203 -0
  245. package/src/capabilities/gen/browser.ts +54 -0
  246. package/src/capabilities/gen/node.ts +43 -0
  247. package/src/capabilities/gen/tauri.ts +54 -0
  248. package/src/capabilities/gen/workerd.ts +43 -0
  249. package/src/capabilities/index.ts +47 -0
  250. package/src/capabilities/operation-handler.ts +16 -0
  251. package/src/capabilities/react-surface.tsx +45 -0
  252. package/src/capabilities/schema.ts +7 -0
  253. package/src/components/DiscordBotStatus/DiscordBotStatus.stories.tsx +47 -0
  254. package/src/components/DiscordBotStatus/DiscordBotStatus.tsx +76 -0
  255. package/src/components/DiscordBotStatus/index.ts +5 -0
  256. package/src/components/DiscordBotToolbar/DiscordBotToolbar.stories.tsx +37 -0
  257. package/src/components/DiscordBotToolbar/DiscordBotToolbar.tsx +69 -0
  258. package/src/components/DiscordBotToolbar/index.ts +5 -0
  259. package/src/components/DiscordChannelForm/DiscordChannelForm.stories.tsx +63 -0
  260. package/src/components/DiscordChannelForm/DiscordChannelForm.tsx +53 -0
  261. package/src/components/DiscordChannelForm/index.ts +5 -0
  262. package/src/components/DiscordSetup/DiscordSetup.tsx +174 -0
  263. package/src/components/DiscordSetup/index.ts +5 -0
  264. package/src/components/index.ts +8 -0
  265. package/src/constants.ts +80 -0
  266. package/src/containers/DiscordAgentSetup/DiscordAgentSetup.tsx +354 -0
  267. package/src/containers/DiscordAgentSetup/index.ts +5 -0
  268. package/src/containers/DiscordChannelProperties/DiscordChannelProperties.tsx +129 -0
  269. package/src/containers/DiscordChannelProperties/index.ts +5 -0
  270. package/src/containers/index.ts +8 -0
  271. package/src/errors.ts +103 -0
  272. package/src/index.ts +7 -0
  273. package/src/meta.ts +9 -0
  274. package/src/operations/admin/bind-channel.ts +103 -0
  275. package/src/operations/admin/bot.ts +36 -0
  276. package/src/operations/admin/guild-channel.ts +35 -0
  277. package/src/operations/admin/list-bindings.ts +25 -0
  278. package/src/operations/admin/list-bot-guilds.ts +25 -0
  279. package/src/operations/admin/list-guild-channels.ts +28 -0
  280. package/src/operations/admin/list-managed-guilds.ts +34 -0
  281. package/src/operations/admin/unbind-channel.ts +69 -0
  282. package/src/operations/crawl.ts +71 -0
  283. package/src/operations/errors.ts +8 -0
  284. package/src/operations/get-discord-channels.ts +91 -0
  285. package/src/operations/index.ts +21 -0
  286. package/src/operations/materialize-target.ts +56 -0
  287. package/src/operations/sync.test.ts +68 -0
  288. package/src/operations/sync.ts +258 -0
  289. package/src/plugin.test.ts +33 -0
  290. package/src/plugin.ts +29 -0
  291. package/src/services/bot-gateway.ts +92 -0
  292. package/src/services/bot-rest.ts +122 -0
  293. package/src/services/crawl-stores.ts +37 -0
  294. package/src/services/discord-source.test.ts +179 -0
  295. package/src/services/discord-source.ts +179 -0
  296. package/src/services/discord.test.ts +43 -0
  297. package/src/services/discord.ts +87 -0
  298. package/src/services/index.ts +19 -0
  299. package/src/services/proxy-http-client.ts +78 -0
  300. package/src/testing/crawl-demo.test.ts +78 -0
  301. package/src/testing/fetch.ts +167 -0
  302. package/src/testing/generate-fixtures.ts +89 -0
  303. package/src/testing/index.ts +5 -0
  304. package/src/testing/questions-demo.test.ts +76 -0
  305. package/src/translations.ts +64 -0
  306. package/src/types/DiscordAdminOperation.ts +149 -0
  307. package/src/types/DiscordChannel.ts +100 -0
  308. package/src/types/DiscordEvents.ts +16 -0
  309. package/src/types/DiscordOperation.ts +106 -0
  310. package/src/types/DiscordTargetOptions.ts +35 -0
  311. package/src/types/index.ts +9 -0
  312. package/src/vite-env.d.ts +10 -0
  313. package/README.md +0 -3
package/LICENSE ADDED
@@ -0,0 +1,105 @@
1
+ # Functional Source License, Version 1.1, ALv2 Future License
2
+
3
+ ## Abbreviation
4
+
5
+ FSL-1.1-Apache-2.0
6
+
7
+ ## Notice
8
+
9
+ Copyright 2026 DXOS
10
+
11
+ ## Terms and Conditions
12
+
13
+ ### Licensor ("We")
14
+
15
+ The party offering the Software under these Terms and Conditions.
16
+
17
+ ### The Software
18
+
19
+ The "Software" is each version of the software that we make available under
20
+ these Terms and Conditions, as indicated by our inclusion of these Terms and
21
+ Conditions with the Software.
22
+
23
+ ### License Grant
24
+
25
+ Subject to your compliance with this License Grant and the Patents,
26
+ Redistribution and Trademark clauses below, we hereby grant you the right to
27
+ use, copy, modify, create derivative works, publicly perform, publicly display
28
+ and redistribute the Software for any Permitted Purpose identified below.
29
+
30
+ ### Permitted Purpose
31
+
32
+ A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
33
+ means making the Software available to others in a commercial product or
34
+ service that:
35
+
36
+ 1. substitutes for the Software;
37
+
38
+ 2. substitutes for any other product or service we offer using the Software
39
+ that exists as of the date we make the Software available; or
40
+
41
+ 3. offers the same or substantially similar functionality as the Software.
42
+
43
+ Permitted Purposes specifically include using the Software:
44
+
45
+ 1. for your internal use and access;
46
+
47
+ 2. for non-commercial education;
48
+
49
+ 3. for non-commercial research; and
50
+
51
+ 4. in connection with professional services that you provide to a licensee
52
+ using the Software in accordance with these Terms and Conditions.
53
+
54
+ ### Patents
55
+
56
+ To the extent your use for a Permitted Purpose would necessarily infringe our
57
+ patents, the license grant above includes a license under our patents. If you
58
+ make a claim against any party that the Software infringes or contributes to
59
+ the infringement of any patent, then your patent license to the Software ends
60
+ immediately.
61
+
62
+ ### Redistribution
63
+
64
+ The Terms and Conditions apply to all copies, modifications and derivatives of
65
+ the Software.
66
+
67
+ If you redistribute any copies, modifications or derivatives of the Software,
68
+ you must include a copy of or a link to these Terms and Conditions and not
69
+ remove any copyright notices provided in or with the Software.
70
+
71
+ ### Disclaimer
72
+
73
+ THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
74
+ IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
75
+ PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
76
+
77
+ IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
78
+ SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
79
+ EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
80
+
81
+ ### Trademarks
82
+
83
+ Except for displaying the License Details and identifying us as the origin of
84
+ the Software, you have no right under these Terms and Conditions to use our
85
+ trademarks, trade names, service marks or product names.
86
+
87
+ ## Grant of Future License
88
+
89
+ We hereby irrevocably grant you an additional license to use the Software under
90
+ the Apache License, Version 2.0 that is effective on the second anniversary of
91
+ the date we make the Software available. On or after that date, you may use the
92
+ Software under the Apache License, Version 2.0, in which case the following
93
+ will apply:
94
+
95
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not use
96
+ this file except in compliance with the License.
97
+
98
+ You may obtain a copy of the License at
99
+
100
+ http://www.apache.org/licenses/LICENSE-2.0
101
+
102
+ Unless required by applicable law or agreed to in writing, software distributed
103
+ under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
104
+ CONDITIONS OF ANY KIND, either express or implied. See the License for the
105
+ specific language governing permissions and limitations under the License.
package/PLUGIN.mdl ADDED
@@ -0,0 +1,494 @@
1
+ ---
2
+ id: org.dxos.plugin.discord
3
+ name: DiscordPlugin
4
+ version: 0.1.0
5
+ ---
6
+
7
+ A Composer plugin that connects a Discord bot to a workspace so that server
8
+ channels stream alongside everything else the user is working on. Messages
9
+ from every guild the bot belongs to are synced into ECHO as `@dxos/types`
10
+ `Message` objects inside `Channel` feeds — fully local-first and replicated
11
+ to all peers.
12
+
13
+ ## Extensions
14
+
15
+ The following extension dialects are used in this document.
16
+ Each extension is defined in the Appendix or resolved via its URI.
17
+
18
+ | Term | URI |
19
+ |-------------|--------------------------------|
20
+ | `type` | `org.dxos.mdl.type@1.0` |
21
+ | `feat` | `org.dxos.mdl.feat@1.0` |
22
+ | `scenario` | `org.dxos.mdl.scenario@1.0` |
23
+ | `component` | `org.dxos.mdl.component@1.0` |
24
+ | `op` | `org.dxos.mdl.op@1.0` |
25
+ | `op@1.1` | `org.dxos.mdl.op@1.1` |
26
+ | `test` | `org.dxos.mdl.test@1.0` |
27
+
28
+ ## Types
29
+
30
+ ```mdl
31
+ type RemoteTarget
32
+ desc: Wire shape returned by GetDiscordChannels representing one Discord text channel.
33
+ fields:
34
+ id: string # Discord channel snowflake id
35
+ name: string # "#channel — Guild" display label
36
+ description?: string # channel topic, if set
37
+ metadata?: Record<string, unknown> # guildId, guildName, channelType, etc.
38
+ ```
39
+
40
+ ```mdl
41
+ type DiscordTargetOptions
42
+ desc: Per-target sync options stored under IntegrationTarget.options and edited from the Integration UI.
43
+ fields:
44
+ maxDays?: number # initial backfill window in days (default 30; ignored after first sync)
45
+ ```
46
+
47
+ ```mdl
48
+ type PullResult
49
+ desc: Summary of a completed incremental sync for one target channel.
50
+ fields:
51
+ added: number # number of new Message objects appended to the channel feed
52
+ ```
53
+
54
+ ```mdl
55
+ type DiscordChannel
56
+ desc: >
57
+ Config of a Discord-backed Channel (`Channel.backend.config`, backend kind
58
+ `org.dxos.channel.backend.discord`): the bot that posts and the Discord channels it listens in.
59
+ EDGE's gateway reads it from the space when the bot starts.
60
+ fields:
61
+ accessToken: Ref<AccessToken> # pasted bot token
62
+ applicationId: string # Discord application (bot) id; keys the EDGE gateway
63
+ guildId?: string
64
+ channels: string[] # Discord channel ids; the first receives posts with no thread
65
+ ```
66
+
67
+ ## Services
68
+
69
+ ```mdl
70
+ service DiscordREST
71
+ desc: >
72
+ dfx DiscordREST service layer. Wraps Discord's REST API v10 with an
73
+ in-memory rate-limit store. All HTTP traffic is routed through the edge
74
+ CORS proxy via makeEdgeProxyHttpClientLayer so browser-side requests work
75
+ without direct Discord API access.
76
+ loading: lazy
77
+ ops:
78
+ getMyUser() → User
79
+ listMyGuilds(params) → Guild[]
80
+ listGuildChannels(guildId: string) → Channel[]
81
+ listMessages(channelId: string, params) → Message[]
82
+ errors:
83
+ ErrorResponse: non-2xx Discord API error (code + message in body)
84
+ RatelimitedResponse: HTTP 429; cause carries retry_after seconds
85
+ note: >
86
+ The layer is constructed per-operation via makeDiscordLayer (integration ref)
87
+ or makeDiscordLayerFromToken (raw token for credential-form validation).
88
+ The CORS proxy is temporary; a future revision will route through the
89
+ authenticated /proxy/* edge endpoint using signed verifiable presentations.
90
+ ```
91
+
92
+ ## Operations
93
+
94
+ ```mdl
95
+ op GetDiscordChannels
96
+ key: org.dxos.operation.discord.getChannels
97
+ desc: >
98
+ Discovery only. Lists every text and announcement channel (type 0 and 5)
99
+ across all guilds the bot belongs to and returns one RemoteTarget per
100
+ channel. Read-only: never creates a local Channel object. Guild pagination
101
+ is drained in full; channel fetch failures for individual guilds are logged
102
+ and skipped so the rest of the discovery result remains usable.
103
+ input:
104
+ connection: Ref<Connection>
105
+ output: { targets: RemoteTarget[] }
106
+ effects: [http]
107
+ requires: [DiscordREST]
108
+ note: >
109
+ Channel names are rendered as "#channel — Guild" so the integration UI can
110
+ disambiguate same-named channels across multiple guilds without a separate
111
+ grouping concept.
112
+ ```
113
+
114
+ ```mdl
115
+ op MaterializeDiscordTarget
116
+ key: org.dxos.operation.discord.materializeTarget
117
+ desc: >
118
+ Find-or-create the empty local feed-backed Channel for a selected Discord
119
+ channel, keyed by the Discord channel id, so a sync cursor can reference it.
120
+ input:
121
+ connection: Ref<Connection>
122
+ remoteTarget: RemoteTarget
123
+ output: { target: Ref<Channel> }
124
+ effects: [echo:write]
125
+ ```
126
+
127
+ ```mdl
128
+ op SyncDiscordChannel
129
+ key: org.dxos.operation.discord.syncChannel
130
+ desc: >
131
+ Pull-only incremental sync for every Discord target bound to a Connection.
132
+ For each bound target: resolves the local ECHO Channel keyed by the Discord
133
+ channel snowflake id, pages through messages newer than target.cursor (or
134
+ from "now minus maxDays" on first sync), maps each Discord message to a
135
+ @dxos/types Message, appends the batch to the channel's ECHO Feed, then
136
+ advances target.cursor to the newest id seen. Targets are processed in
137
+ parallel (concurrency 3). Failures on one target record lastError on that
138
+ target and continue; the overall op only fails if the outer guild-list or
139
+ Discord layer setup fails.
140
+ input:
141
+ connection: Ref<Connection>
142
+ priority?: string # cursor id of the binding to sync first
143
+ output: { pulled: PullResult }
144
+ effects: [http, echo:write]
145
+ requires: [DiscordREST]
146
+ note: >
147
+ Message mapping skips non-DEFAULT/non-REPLY types (joins, pins, calls, …)
148
+ to keep the feed clean. Discord embeds and attachments are not yet mapped
149
+ to ContentBlock — plain text only for now. A toast is emitted on success
150
+ or failure via LayoutOperation.AddToast.
151
+ ```
152
+
153
+ ## Channel backend
154
+
155
+ ```mdl
156
+ service DiscordChannelBackend
157
+ desc: >
158
+ plugin-thread `ChannelBackendProvider` for Discord channels, so agents and other callers reach
159
+ Discord through plugin-thread's generic channel operations rather than Discord-specific ones.
160
+ ops:
161
+ send(channel, message) # REST post into the first configured Discord channel
162
+ threads.send(channel, thread, msg) # REST post into a thread or DM channel id
163
+ openDirect(channel, person) # DM channel from the person's `discord` identity
164
+ connection.start/stop/status # PUT/DELETE/GET /compute/discord/bots/:applicationId on EDGE
165
+ note: >
166
+ Posts use plain fetch (no proxy) so they also run on EDGE; text past 2000 characters is split,
167
+ and mentions are never echoed. The receipt carries `properties.discord.messageId`, which EDGE's
168
+ reply mirror reads to skip posts the agent already made. The Channel's ObjectProperties surface
169
+ renders the config form, the bot status and Start/Stop.
170
+ ```
171
+
172
+ ## Features
173
+
174
+ ```mdl
175
+ feat F-1: Bot Identity and Credential Setup
176
+
177
+ req F-1.1: User provides a bot token from the Discord developer portal via the credential form.
178
+ req F-1.2:
179
+ when: user submits the credential form
180
+ then: token is validated against GET /users/@me before any Integration is persisted
181
+ errors: [InvalidToken: Discord rejected the token with 401]
182
+ req F-1.3:
183
+ when: token is valid
184
+ then: bot's display name (global_name ?? username) is stored on AccessToken.account
185
+ req F-1.4: AccessToken is stored in ECHO and referenced by the Integration; the raw token is never re-exposed after setup.
186
+ ```
187
+
188
+ ```mdl
189
+ feat F-2: Channel Discovery
190
+
191
+ req F-2.1:
192
+ when: user opens the Integration target picker
193
+ then: op:GetDiscordChannels is invoked and returns one entry per reachable text/announcement channel
194
+ req F-2.2: Entries are labeled "#channel — Guild" to disambiguate channels with identical names across guilds.
195
+ req F-2.3:
196
+ when: a guild's channel list cannot be fetched (bot removed, network error)
197
+ then: that guild's channels are omitted from results; other guilds are unaffected
198
+ req F-2.4: User selects one or more channels as Integration targets; unselected channels leave no trace in the space.
199
+ ```
200
+
201
+ ```mdl
202
+ feat F-3: Incremental Message Sync
203
+
204
+ req F-3.1:
205
+ when: op:SyncDiscordChannel is invoked for an Integration
206
+ then: for each selected target channel, messages newer than target.cursor are fetched and appended
207
+ req F-3.2:
208
+ when: a target has no cursor (first sync)
209
+ then: lookback window is determined by DiscordTargetOptions.maxDays (default 30, max ~3 years)
210
+ req F-3.3:
211
+ when: sync succeeds for a target
212
+ then: target.cursor advances to the newest message snowflake id seen; subsequent runs are incremental
213
+ req F-3.4:
214
+ when: sync fails for a target
215
+ then: target.lastError is set with a human-readable description; other targets continue unaffected
216
+ req F-3.5: System messages (joins, pins, calls) are filtered out; only DEFAULT (type 0) and REPLY (type 19) messages are stored.
217
+ req F-3.6: Discord bot replies carry the referenced message id in Message.threadId to enable reply-chain reconstruction.
218
+ ```
219
+
220
+ ```mdl
221
+ feat F-4: Channel Materialization
222
+
223
+ req F-4.1:
224
+ when: a target channel is synced for the first time
225
+ then: a local ECHO Channel keyed by the Discord channel snowflake id is created in the space
226
+ req F-4.2: Channel keying uses Obj.Meta foreign-key [{source: 'discord.com', id: channelSnowflakeId}]; re-running sync on the same channel is idempotent.
227
+ req F-4.3:
228
+ when: the channel's name changes on Discord
229
+ then: the local Channel.name is updated to the "#channel — Guild" label on the next sync
230
+ req F-4.4:
231
+ when: a target ref exists from a legacy integration (remoteId not set)
232
+ then: the Discord channel id is recovered from the Channel's foreign-key metadata rather than the target entry
233
+ ```
234
+
235
+ ```mdl
236
+ feat F-5: ECHO Persistence and Replication
237
+
238
+ req F-5.1: Each synced Message is appended to the Channel's ECHO Feed via Feed.append.
239
+ req F-5.2:
240
+ when: a peer receives a Message-feed update
241
+ then: the channel's message list reflects the new messages without additional sync
242
+ tags: [collaborative]
243
+ req F-5.3: All ECHO mutations (Channel creation, Message append, cursor update) are transacted per-target so a mid-sync crash cannot leave the space in a partially-updated state.
244
+ ```
245
+
246
+ ## Acceptance
247
+
248
+ ```mdl
249
+ scenario T-1: Valid bot token accepted
250
+ given: user opens the Discord integration credential form
251
+ when: user pastes a valid bot token and submits
252
+ then:
253
+ - GET /users/@me returns 200
254
+ - AccessToken created with account = bot display name
255
+ - Integration created with targets: []
256
+ ```
257
+
258
+ ```mdl
259
+ scenario T-2: Invalid token rejected
260
+ given: user opens the credential form
261
+ when: user pastes an invalid token (application id, expired token, etc.)
262
+ then:
263
+ - Discord returns 401
264
+ - Form shows error: 'Discord rejected the token (401). Reset the bot token…'
265
+ - No AccessToken or Integration is persisted
266
+ ```
267
+
268
+ ```mdl
269
+ scenario T-3: Channel discovery lists all guilds
270
+ given: a valid Integration whose bot belongs to two guilds, each with two text channels
271
+ when: op:GetDiscordChannels is invoked
272
+ then:
273
+ - output.targets contains four entries, one per channel
274
+ - each entry has id, name ("#channel — Guild"), and optional description
275
+ ```
276
+
277
+ ```mdl
278
+ scenario T-4: Channel discovery skips failed guild
279
+ given: bot belongs to two guilds; guild B's channel list returns 403
280
+ when: op:GetDiscordChannels is invoked
281
+ then:
282
+ - targets contains only guild A's channels
283
+ - no error is raised; guild B failure is logged and skipped
284
+ ```
285
+
286
+ ```mdl
287
+ scenario T-5: First sync materializes channel and appends messages
288
+ given: Integration with one selected target (no cursor); Discord returns 10 messages
289
+ when: op:SyncDiscordChannel is invoked
290
+ then:
291
+ - local Channel created in space with source=discord.com foreign key
292
+ - 10 Message objects appended to Channel.feed
293
+ - target.cursor set to the newest message snowflake id
294
+ - output.pulled.added === 10
295
+ ```
296
+
297
+ ```mdl
298
+ scenario T-6: Incremental sync fetches only new messages
299
+ given: target.cursor is set to snowflake S; Discord returns 3 messages after S
300
+ when: op:SyncDiscordChannel is invoked
301
+ then:
302
+ - only messages with id > S are fetched
303
+ - 3 new Messages appended; existing Messages unchanged
304
+ - target.cursor advances to the newest of the 3 ids
305
+ - output.pulled.added === 3
306
+ ```
307
+
308
+ ```mdl
309
+ scenario T-7: System messages filtered out
310
+ given: Discord returns 5 messages: 2 DEFAULT, 1 REPLY, 1 JOIN (type 7), 1 PIN (type 6)
311
+ when: op:SyncDiscordChannel maps the batch
312
+ then:
313
+ - 3 Messages appended (DEFAULT + REPLY only)
314
+ - JOIN and PIN messages discarded
315
+ ```
316
+
317
+ ```mdl
318
+ scenario T-8: Sync failure recorded per target
319
+ given: Integration with two selected targets; target B's channel returns 403
320
+ when: op:SyncDiscordChannel is invoked
321
+ then:
322
+ - target A syncs successfully; its cursor advances
323
+ - target B.lastError is set with a human-readable Discord error string
324
+ - op returns without throwing; output.pulled.added counts only target A's messages
325
+ ```
326
+
327
+ ```mdl
328
+ scenario T-9: Channel re-sync is idempotent
329
+ given: Channel already exists for discord channel id X with 5 Messages
330
+ when: op:SyncDiscordChannel is invoked again with same cursor
331
+ then:
332
+ - no duplicate Channel created (foreign-key lookup returns existing Channel)
333
+ - 0 Messages appended (no new messages beyond cursor)
334
+ - output.pulled.added === 0
335
+ ```
336
+
337
+ ```mdl
338
+ scenario T-10: maxDays controls initial lookback
339
+ given: new target with DiscordTargetOptions.maxDays = 14
340
+ when: op:SyncDiscordChannel is invoked for the first time
341
+ then:
342
+ - after parameter is a snowflake derived from (now - 14 days)
343
+ - Messages older than 14 days are not fetched
344
+ ```
345
+
346
+ ---
347
+
348
+ ## QA
349
+
350
+ Executable tests. Each step carries a human instruction (`do`) and, where the step maps to an
351
+ invocable operation, an agent binding (`invoke`). Report failures by test and step number.
352
+
353
+ plugin-discord is not in `getDefaults` or the dev-only default set (see
354
+ `packages/apps/composer-app/src/plugin-defs.tsx`) — it is OFF by default. Its whole surface
355
+ depends on a real Discord bot token, which no agent session holds, so the credential and discovery
356
+ steps are `blocked` per Execution Rule 9; only the sync step, given a pre-existing Integration, can
357
+ run unattended.
358
+
359
+ ```mdl
360
+ test QA-1: Connect a bot and sync a channel
361
+ desc: The core Discord round-trip — add the credential, discover channels, select one, sync messages.
362
+ actors: human
363
+ covers: [F-1.1, F-1.2, F-2.1, F-3.1, F-4.1, T-1, T-3, T-5]
364
+ status: blocked
365
+ given:
366
+ - an identity exists
367
+ - space: a space is open
368
+ - plugin-discord is enabled — it is OFF by default
369
+ - connection: a real Discord bot token, invited to at least one guild with a text channel,
370
+ bound as a Connection
371
+ steps:
372
+ - name: Add the Discord integration credential
373
+ do: Open Settings > Integrations > Discord, and paste a bot token from the Discord developer portal.
374
+ expect: the token validates against GET /users/@me and a Connection is created with targets [] and the bot's display name
375
+ blocked: |
376
+ Requires a real Discord bot token — no agent session holds credentials for an external
377
+ service. A human tester must run this step manually.
378
+ - name: Discover channels
379
+ do: Open the Integration's target picker.
380
+ invoke: [op:GetDiscordChannels] { connection: $given.connection }
381
+ capture: discovered
382
+ expect: one entry per reachable text/announcement channel, labeled "#channel — Guild"
383
+ blocked: Depends on the previous step's Connection, which is itself blocked without a real token.
384
+ - name: Select a channel and materialize its local Channel
385
+ do: Select one channel from the discovery list and confirm.
386
+ invoke: [op:MaterializeDiscordTarget] { connection: $given.connection, remoteTarget: $discovered.targets[0] }
387
+ capture: materialized
388
+ expect: a local Channel bound to the selected Discord channel now exists
389
+ blocked: Same dependency chain — cannot run without a live Connection.
390
+ - name: Sync the bound targets
391
+ do: (no separate gesture — sync runs automatically once a target is bound, or via a manual "Sync now" action)
392
+ invoke: [op:SyncDiscordChannel] { connection: $given.connection }
393
+ capture: synced
394
+ expect: the materialized Channel's recent messages appear in the feed
395
+ assert: return $synced.pulled.added >= 0
396
+ blocked: Same dependency chain.
397
+ after:
398
+ - name: Remove the synced channel and integration
399
+ do: Delete the synced Channel object and remove the Discord Integration from Settings.
400
+ invoke: org.dxos.operation.space.removeObjects { refs: [$materialized.target] }
401
+ expect: the Channel and its messages are gone, and the Integration no longer lists the target
402
+ blocked: Same dependency chain.
403
+ ```
404
+
405
+ ```mdl
406
+ test QA-2: Re-sync is idempotent
407
+ desc: |
408
+ The incremental-sync guarantee (F-3.3, F-4.2) against an Integration that already exists —
409
+ still requires a live bot token, so this is also blocked absent one, but is written separately
410
+ since it exercises different requirements than QA-1's first sync.
411
+ actors: agent
412
+ covers: [F-3.3, F-4.2, T-6, T-9]
413
+ status: blocked
414
+ given:
415
+ - an identity exists
416
+ - connection: a Discord Connection with at least one selected target already synced once
417
+ (see QA-1)
418
+ steps:
419
+ - name: Re-run sync against the same cursor
420
+ do: Trigger a sync again without any new Discord activity.
421
+ invoke: [op:SyncDiscordChannel] { connection: $given.connection }
422
+ expect: no duplicate Channel is created and pulled.added is 0
423
+ assert: return $result.pulled.added === 0
424
+ blocked: Requires a live Discord bot token and an already-synced Integration (see QA-1).
425
+ ```
426
+
427
+ ---
428
+
429
+ ## Appendix: Extension Definitions
430
+
431
+ Extension block types used in this document are defined below using
432
+ the core `ext` primitive — the only construct the base language provides.
433
+
434
+ ```mdl
435
+ ext type
436
+ uri: org.dxos.mdl.type@1.0
437
+ desc: A named data structure with typed fields and optional literals.
438
+ fields:
439
+ desc?: Prose
440
+ fields?: FieldMap # name[?]: TypeExpr (# inline comment)
441
+ literals?: UnionList # a | b | c
442
+ extends?: TypeRef[]
443
+ ```
444
+
445
+ ```mdl
446
+ ext feat
447
+ uri: org.dxos.mdl.feat@1.0
448
+ desc: A named feature grouping one or more requirements.
449
+ fields:
450
+ desc?: Prose
451
+ req: RequirementList
452
+ nesting: self # feat blocks may contain feat blocks
453
+ ```
454
+
455
+ ```mdl
456
+ ext scenario
457
+ uri: org.dxos.mdl.scenario@1.0
458
+ desc: An acceptance scenario expressed as given / when / then steps.
459
+ fields:
460
+ given?: Step | Step[]
461
+ when?: Step | Step[]
462
+ then: Step | Step[]
463
+ tags?: TagList
464
+ ```
465
+
466
+ ```mdl
467
+ ext component
468
+ uri: org.dxos.mdl.component@1.0
469
+ desc: A UI component with props, internal state, slots, actions, and events.
470
+ fields:
471
+ desc?: Prose
472
+ props?: FieldMap # external inputs (immutable inside component)
473
+ state?: FieldMap # internal reactive state
474
+ slots?: FieldMap # named ReactNode injection points
475
+ actions?: ActionMap # methods the component exposes or handles
476
+ emits?: EventMap # events the component raises to its parent
477
+ layout?: CodeBlock # ASCII sketch of visual structure (non-normative)
478
+ ```
479
+
480
+ ```mdl
481
+ ext op
482
+ uri: org.dxos.mdl.op@1.0
483
+ desc: |
484
+ A named operation with typed inputs, outputs, and declared errors.
485
+ Pure ops have no effects or requires. Effectful ops declare both.
486
+ fields:
487
+ desc?: Prose
488
+ input?: FieldMap # named input parameters
489
+ output?: TypeExpr # return type
490
+ errors?: ErrorMap # name: Prose (when this error occurs)
491
+ effects?: EffectList # echo:read | echo:write | http | fs | ...
492
+ requires?: ServiceList # injected service dependencies
493
+ note?: Prose # implementation guidance (non-normative)
494
+ ```