@open-everywhere/provider-discord 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +21 -0
  3. package/README.md +47 -0
  4. package/agent/access/discord-grant-click.ts +237 -0
  5. package/agent/approvals/discord-admin.ts +227 -0
  6. package/agent/approvals/discord-card.ts +248 -0
  7. package/agent/approvals/discord-flow.ts +469 -0
  8. package/agent/approvals/discord-scope-label.ts +27 -0
  9. package/agent/channels/discord-approval-card.ts +373 -0
  10. package/agent/channels/discord-backscroll.ts +593 -0
  11. package/agent/channels/discord-bot-name.ts +113 -0
  12. package/agent/channels/discord-channel-info.ts +83 -0
  13. package/agent/channels/discord-directory.ts +179 -0
  14. package/agent/channels/discord-dispatch-target.ts +103 -0
  15. package/agent/channels/discord-events.ts +175 -0
  16. package/agent/channels/discord-front.ts +355 -0
  17. package/agent/channels/discord-gateway.ts +3256 -0
  18. package/agent/channels/discord-identity.ts +269 -0
  19. package/agent/channels/discord-interaction-defer.ts +123 -0
  20. package/agent/channels/discord-message-ops.ts +127 -0
  21. package/agent/channels/discord-origin-codec.ts +105 -0
  22. package/agent/channels/discord-post-arm.ts +230 -0
  23. package/agent/channels/discord-post.ts +463 -0
  24. package/agent/channels/discord-read.ts +140 -0
  25. package/agent/channels/discord-rest.ts +85 -0
  26. package/agent/channels/discord.ts +244 -0
  27. package/agent/commands/discord-command-sync.ts +285 -0
  28. package/agent/commands/discord-commands.ts +291 -0
  29. package/agent/contributions.ts +31 -0
  30. package/agent/docs-generated.ts +15 -0
  31. package/agent/identity/discord-link-consent.ts +124 -0
  32. package/agent/ops.ts +14 -0
  33. package/agent/provider-events.ts +86 -0
  34. package/agent/register.ts +147 -0
  35. package/agent/surface-parity.ts +95 -0
  36. package/dist/agent/access/discord-grant-click.d.ts +25 -0
  37. package/dist/agent/access/discord-grant-click.d.ts.map +1 -0
  38. package/dist/agent/access/discord-grant-click.js +210 -0
  39. package/dist/agent/access/discord-grant-click.js.map +1 -0
  40. package/dist/agent/approvals/discord-admin.d.ts +63 -0
  41. package/dist/agent/approvals/discord-admin.d.ts.map +1 -0
  42. package/dist/agent/approvals/discord-admin.js +219 -0
  43. package/dist/agent/approvals/discord-admin.js.map +1 -0
  44. package/dist/agent/approvals/discord-card.d.ts +102 -0
  45. package/dist/agent/approvals/discord-card.d.ts.map +1 -0
  46. package/dist/agent/approvals/discord-card.js +217 -0
  47. package/dist/agent/approvals/discord-card.js.map +1 -0
  48. package/dist/agent/approvals/discord-flow.d.ts +109 -0
  49. package/dist/agent/approvals/discord-flow.d.ts.map +1 -0
  50. package/dist/agent/approvals/discord-flow.js +375 -0
  51. package/dist/agent/approvals/discord-flow.js.map +1 -0
  52. package/dist/agent/approvals/discord-scope-label.d.ts +3 -0
  53. package/dist/agent/approvals/discord-scope-label.d.ts.map +1 -0
  54. package/dist/agent/approvals/discord-scope-label.js +27 -0
  55. package/dist/agent/approvals/discord-scope-label.js.map +1 -0
  56. package/dist/agent/channels/discord-approval-card.d.ts +81 -0
  57. package/dist/agent/channels/discord-approval-card.d.ts.map +1 -0
  58. package/dist/agent/channels/discord-approval-card.js +303 -0
  59. package/dist/agent/channels/discord-approval-card.js.map +1 -0
  60. package/dist/agent/channels/discord-backscroll.d.ts +209 -0
  61. package/dist/agent/channels/discord-backscroll.d.ts.map +1 -0
  62. package/dist/agent/channels/discord-backscroll.js +505 -0
  63. package/dist/agent/channels/discord-backscroll.js.map +1 -0
  64. package/dist/agent/channels/discord-bot-name.d.ts +19 -0
  65. package/dist/agent/channels/discord-bot-name.d.ts.map +1 -0
  66. package/dist/agent/channels/discord-bot-name.js +98 -0
  67. package/dist/agent/channels/discord-bot-name.js.map +1 -0
  68. package/dist/agent/channels/discord-channel-info.d.ts +22 -0
  69. package/dist/agent/channels/discord-channel-info.d.ts.map +1 -0
  70. package/dist/agent/channels/discord-channel-info.js +65 -0
  71. package/dist/agent/channels/discord-channel-info.js.map +1 -0
  72. package/dist/agent/channels/discord-directory.d.ts +11 -0
  73. package/dist/agent/channels/discord-directory.d.ts.map +1 -0
  74. package/dist/agent/channels/discord-directory.js +170 -0
  75. package/dist/agent/channels/discord-directory.js.map +1 -0
  76. package/dist/agent/channels/discord-dispatch-target.d.ts +15 -0
  77. package/dist/agent/channels/discord-dispatch-target.d.ts.map +1 -0
  78. package/dist/agent/channels/discord-dispatch-target.js +99 -0
  79. package/dist/agent/channels/discord-dispatch-target.js.map +1 -0
  80. package/dist/agent/channels/discord-events.d.ts +58 -0
  81. package/dist/agent/channels/discord-events.d.ts.map +1 -0
  82. package/dist/agent/channels/discord-events.js +150 -0
  83. package/dist/agent/channels/discord-events.js.map +1 -0
  84. package/dist/agent/channels/discord-front.d.ts +115 -0
  85. package/dist/agent/channels/discord-front.d.ts.map +1 -0
  86. package/dist/agent/channels/discord-front.js +279 -0
  87. package/dist/agent/channels/discord-front.js.map +1 -0
  88. package/dist/agent/channels/discord-gateway.d.ts +783 -0
  89. package/dist/agent/channels/discord-gateway.d.ts.map +1 -0
  90. package/dist/agent/channels/discord-gateway.js +2591 -0
  91. package/dist/agent/channels/discord-gateway.js.map +1 -0
  92. package/dist/agent/channels/discord-identity.d.ts +110 -0
  93. package/dist/agent/channels/discord-identity.d.ts.map +1 -0
  94. package/dist/agent/channels/discord-identity.js +201 -0
  95. package/dist/agent/channels/discord-identity.js.map +1 -0
  96. package/dist/agent/channels/discord-interaction-defer.d.ts +33 -0
  97. package/dist/agent/channels/discord-interaction-defer.d.ts.map +1 -0
  98. package/dist/agent/channels/discord-interaction-defer.js +102 -0
  99. package/dist/agent/channels/discord-interaction-defer.js.map +1 -0
  100. package/dist/agent/channels/discord-message-ops.d.ts +3 -0
  101. package/dist/agent/channels/discord-message-ops.d.ts.map +1 -0
  102. package/dist/agent/channels/discord-message-ops.js +123 -0
  103. package/dist/agent/channels/discord-message-ops.js.map +1 -0
  104. package/dist/agent/channels/discord-origin-codec.d.ts +7 -0
  105. package/dist/agent/channels/discord-origin-codec.d.ts.map +1 -0
  106. package/dist/agent/channels/discord-origin-codec.js +98 -0
  107. package/dist/agent/channels/discord-origin-codec.js.map +1 -0
  108. package/dist/agent/channels/discord-post-arm.d.ts +3 -0
  109. package/dist/agent/channels/discord-post-arm.d.ts.map +1 -0
  110. package/dist/agent/channels/discord-post-arm.js +195 -0
  111. package/dist/agent/channels/discord-post-arm.js.map +1 -0
  112. package/dist/agent/channels/discord-post.d.ts +120 -0
  113. package/dist/agent/channels/discord-post.d.ts.map +1 -0
  114. package/dist/agent/channels/discord-post.js +426 -0
  115. package/dist/agent/channels/discord-post.js.map +1 -0
  116. package/dist/agent/channels/discord-read.d.ts +40 -0
  117. package/dist/agent/channels/discord-read.d.ts.map +1 -0
  118. package/dist/agent/channels/discord-read.js +128 -0
  119. package/dist/agent/channels/discord-read.js.map +1 -0
  120. package/dist/agent/channels/discord-rest.d.ts +33 -0
  121. package/dist/agent/channels/discord-rest.d.ts.map +1 -0
  122. package/dist/agent/channels/discord-rest.js +75 -0
  123. package/dist/agent/channels/discord-rest.js.map +1 -0
  124. package/dist/agent/channels/discord.d.ts +3 -0
  125. package/dist/agent/channels/discord.d.ts.map +1 -0
  126. package/dist/agent/channels/discord.js +230 -0
  127. package/dist/agent/channels/discord.js.map +1 -0
  128. package/dist/agent/commands/discord-command-sync.d.ts +66 -0
  129. package/dist/agent/commands/discord-command-sync.d.ts.map +1 -0
  130. package/dist/agent/commands/discord-command-sync.js +224 -0
  131. package/dist/agent/commands/discord-command-sync.js.map +1 -0
  132. package/dist/agent/commands/discord-commands.d.ts +45 -0
  133. package/dist/agent/commands/discord-commands.d.ts.map +1 -0
  134. package/dist/agent/commands/discord-commands.js +266 -0
  135. package/dist/agent/commands/discord-commands.js.map +1 -0
  136. package/dist/agent/contributions.d.ts +17 -0
  137. package/dist/agent/contributions.d.ts.map +1 -0
  138. package/dist/agent/contributions.js +31 -0
  139. package/dist/agent/contributions.js.map +1 -0
  140. package/dist/agent/docs-generated.d.ts +3 -0
  141. package/dist/agent/docs-generated.d.ts.map +1 -0
  142. package/dist/agent/docs-generated.js +13 -0
  143. package/dist/agent/docs-generated.js.map +1 -0
  144. package/dist/agent/identity/discord-link-consent.d.ts +13 -0
  145. package/dist/agent/identity/discord-link-consent.d.ts.map +1 -0
  146. package/dist/agent/identity/discord-link-consent.js +111 -0
  147. package/dist/agent/identity/discord-link-consent.js.map +1 -0
  148. package/dist/agent/ops.d.ts +2 -0
  149. package/dist/agent/ops.d.ts.map +1 -0
  150. package/dist/agent/ops.js +13 -0
  151. package/dist/agent/ops.js.map +1 -0
  152. package/dist/agent/provider-events.d.ts +3 -0
  153. package/dist/agent/provider-events.d.ts.map +1 -0
  154. package/dist/agent/provider-events.js +78 -0
  155. package/dist/agent/provider-events.js.map +1 -0
  156. package/dist/agent/register.d.ts +5 -0
  157. package/dist/agent/register.d.ts.map +1 -0
  158. package/dist/agent/register.js +125 -0
  159. package/dist/agent/register.js.map +1 -0
  160. package/dist/agent/surface-parity.d.ts +3 -0
  161. package/dist/agent/surface-parity.d.ts.map +1 -0
  162. package/dist/agent/surface-parity.js +90 -0
  163. package/dist/agent/surface-parity.js.map +1 -0
  164. package/package.json +185 -0
  165. package/tsconfig.build.json +22 -0
  166. package/tsconfig.json +15 -0
@@ -0,0 +1,224 @@
1
+ // THE DISCORD HALF of the surface-command reconcile plane (OPE-1687) — the deployment registers its
2
+ // own "/oe" command instead of waiting for an operator to run a CLI.
3
+ //
4
+ // WHAT IT DOES, ONCE PER PROCESS START: read the commands Discord holds for this application at each
5
+ // declared guild, project both sides onto the fields this deployment actually declares, and write
6
+ // ONLY when they differ. No difference ⇒ no write, so a warm deployment's steady-state cost is one
7
+ // vendor read per cold start.
8
+ //
9
+ // 🔴 THE WRITE IS ADDITIVE — `POST …/commands`, NEVER the bulk `PUT`. Discord's bulk PUT is a full
10
+ // OVERWRITE of every command at that scope, which is the right contract for the operator CLI
11
+ // (`scripts/register-discord-commands.ts`, run deliberately, documented as destructive) and the wrong
12
+ // one for an unattended boot. `docs/deploy/surfaces/discord.md` tells operators to register their own
13
+ // `/ask` command by hand at this same scope; a boot-time PUT of our one-command array would delete it
14
+ // silently on the next cold start. The per-command POST creates-or-updates by NAME and leaves every
15
+ // other command at the scope untouched, so this plane can only ever add or correct "/oe".
16
+ //
17
+ // 🔴 THE DIFF IS A PROJECTION, NOT A DEEP EQUAL. Discord's GET returns fields nobody declared — `id`,
18
+ // `application_id`, `version`, `guild_id`, `default_member_permissions`, `nsfw`, localisation maps —
19
+ // and omits `required` where it is false. A deep equal against the declared body would therefore
20
+ // differ EVERY time and turn "write only on a difference" into a write on every cold start. Both
21
+ // sides go through the same `projectCommand`, which keeps exactly the fields
22
+ // `buildOeDiscordCommandDefinition` sets and fills `required` with its Discord default.
23
+ //
24
+ // NEVER THROWS, AND NEVER BLOCKS BOOT FOR LONG. Every failure — no credential, a refused request, a
25
+ // timeout, malformed JSON — answers with an outcome the boot plane logs. A bot token never reaches an
26
+ // outcome's `detail`, and neither does a response body: a 401 says "401", not what Discord wrote back.
27
+ // The latency bound is two constants below, and both are load-bearing rather than tuning: no 429
28
+ // backoff at all, and one budget for the whole reconcile whatever the guild count.
29
+ //
30
+ // 🔴 ANY PROCESS THAT STARTS WITH BOTH CREDENTIALS WRITES TO THE DECLARED GUILD. The credential pair
31
+ // is the only gate, so a local `eve dev`/`eve start` run with the deployment's own `DISCORD_BOT_TOKEN`
32
+ // registers THAT CHECKOUT's command shape into the declared guild — see the warning in
33
+ // `agent/commands/discord-commands.ts`.
34
+ import { discordFetch } from "../channels/discord-rest.js";
35
+ import { buildOeDiscordCommandDefinition } from "./discord-commands.js";
36
+ /**
37
+ * 🔴 NO 429 BACKOFF AT ALL — the reconcile sits on the FIRST REQUEST's latency path.
38
+ *
39
+ * `withRetryAfter`'s defaults allow up to four attempts, a 30 s single wait and a 60 s total
40
+ * (`packages/core/agent/channels/rate-limit-retry.ts`), and its own header says a latency-critical
41
+ * caller must pass a tighter policy. A boot on a lambda is the strictest such caller there is: the
42
+ * cold start it rides is usually a Discord INTERACTION, which must be acknowledged inside 3 seconds.
43
+ * A 429 answered with `retry_after: 60` would otherwise sleep 30 s inside the boot's own top-level
44
+ * await, blow that ack, and possibly time the invocation out. So a rate-limited Discord is a plain
45
+ * failure here — the next cold start reads again, and the command list is stale meanwhile, which is
46
+ * the state it was already in.
47
+ *
48
+ * The same reasoning is why `discord-gateway.ts` gives its two ephemeral affordances `AFFORDANCE_RETRY`.
49
+ */
50
+ const BOOT_RETRY = { maxAttempts: 1 };
51
+ /** How long ONE Discord call may take. `AbortSignal.timeout` bounds a call, never a backoff sleep —
52
+ * that is what {@link BOOT_RETRY} removes. */
53
+ const CALL_TIMEOUT_MS = 1_500;
54
+ /**
55
+ * The ceiling on the WHOLE reconcile, across every declared guild and both of each guild's calls.
56
+ * Without it the worst case multiplies by declared guild, and "bounded" would depend on how many
57
+ * rooms an operator listed. A guild the budget cannot reach reports `failed` and is read again on the
58
+ * next cold start.
59
+ */
60
+ const RECONCILE_BUDGET_MS = 2_000;
61
+ const API_BASE = "https://discord.com/api/v10";
62
+ function isObject(v) {
63
+ return typeof v === "object" && v !== null;
64
+ }
65
+ function str(v) {
66
+ return typeof v === "string" ? v : "";
67
+ }
68
+ function numOrUndefined(v) {
69
+ return typeof v === "number" && Number.isFinite(v) ? v : undefined;
70
+ }
71
+ function projectOptions(raw) {
72
+ if (!Array.isArray(raw))
73
+ return [];
74
+ const out = [];
75
+ for (const entry of raw) {
76
+ if (!isObject(entry))
77
+ continue;
78
+ const min = numOrUndefined(entry["min_value"]);
79
+ const max = numOrUndefined(entry["max_value"]);
80
+ out.push({
81
+ name: str(entry["name"]),
82
+ description: str(entry["description"]),
83
+ type: numOrUndefined(entry["type"]) ?? 0,
84
+ // Discord OMITS `required` when it is false, so the default is part of the projection. Without
85
+ // it every optional option reads as a difference and the reconcile writes on every cold start.
86
+ required: entry["required"] === true,
87
+ ...(min === undefined ? {} : { min_value: min }),
88
+ ...(max === undefined ? {} : { max_value: max }),
89
+ options: projectOptions(entry["options"]),
90
+ });
91
+ }
92
+ return out;
93
+ }
94
+ /**
95
+ * Project one ApplicationCommand — declared or vendor-held — onto the fields this deployment sets.
96
+ * Returns undefined for anything that is not an object with a name, which is what a malformed or
97
+ * unexpected list entry looks like.
98
+ *
99
+ * OPTION ORDER IS PART OF THE PROJECTION. Discord renders a command's options in the order they were
100
+ * registered, so a reordered list is a real user-visible difference and must be written. Discord
101
+ * returns them in the registered order, so a steady state never reorders on its own.
102
+ */
103
+ export function projectCommand(raw) {
104
+ if (!isObject(raw))
105
+ return undefined;
106
+ const name = str(raw["name"]);
107
+ if (name.length === 0)
108
+ return undefined;
109
+ return {
110
+ name,
111
+ // Discord's own default for `type` is 1 (CHAT_INPUT); the declaration sets it explicitly.
112
+ type: numOrUndefined(raw["type"]) ?? 1,
113
+ description: str(raw["description"]),
114
+ options: projectOptions(raw["options"]),
115
+ };
116
+ }
117
+ /** A stable string for one projected command, so two projections compare as text. */
118
+ function canonical(command) {
119
+ const opt = (o) => [o.name, o.description, o.type, o.required, o.min_value ?? null, o.max_value ?? null, o.options.map(opt)];
120
+ return JSON.stringify([command.name, command.type, command.description, command.options.map(opt)]);
121
+ }
122
+ /**
123
+ * Compare the command this deployment DECLARES against the list Discord HOLDS at one scope.
124
+ *
125
+ * `held` is the raw array Discord's GET returned. Only the entry whose name matches the declared
126
+ * command is considered: every other command at that scope belongs to someone else and this plane
127
+ * neither reads nor writes it.
128
+ */
129
+ export function discordCommandDrift(declared, held) {
130
+ const want = projectCommand(declared);
131
+ if (want === undefined) {
132
+ // A declaration this file cannot project is a code defect, not a vendor state. Refusing to write
133
+ // is the safe half: a bad body would replace a working command with a broken one.
134
+ return { changed: false, reason: "the declared command has no name — nothing was compared and nothing will be written" };
135
+ }
136
+ const list = Array.isArray(held) ? held : [];
137
+ const mine = list.map((entry) => projectCommand(entry)).find((entry) => entry !== undefined && entry.name === want.name);
138
+ if (mine === undefined) {
139
+ return { changed: true, reason: `"/${want.name}" is not registered at this scope` };
140
+ }
141
+ if (canonical(mine) !== canonical(want)) {
142
+ return { changed: true, reason: `"/${want.name}" is registered with a different shape` };
143
+ }
144
+ return { changed: false, reason: `"/${want.name}" already matches what this deployment declares` };
145
+ }
146
+ /** Read + compare + (only on a difference) write, for ONE guild. Never throws. `msLeft` is what is
147
+ * left of the whole reconcile's budget, so no single guild can spend more than the rest of them. */
148
+ async function reconcileOneGuild(guildId, applicationId, botToken, deps, msLeft) {
149
+ const declared = buildOeDiscordCommandDefinition();
150
+ const url = `${API_BASE}/applications/${applicationId}/guilds/${guildId}/commands`;
151
+ const headers = { authorization: `Bot ${botToken}`, "content-type": "application/json" };
152
+ const cap = deps.timeoutMs ?? CALL_TIMEOUT_MS;
153
+ const fetchDeps = { ...(deps.fetch ? { fetch: deps.fetch } : {}), retry: BOOT_RETRY };
154
+ /** This call's own ceiling: the per-call cap, or whatever is left of the reconcile's budget. */
155
+ const nextTimeout = () => Math.min(cap, msLeft());
156
+ if (nextTimeout() <= 0) {
157
+ return { status: "failed", detail: `guild ${guildId}: the boot budget was already spent on an earlier guild — read again on the next start` };
158
+ }
159
+ let held;
160
+ try {
161
+ const res = await discordFetch("GET /applications/{id}/guilds/{id}/commands", url, { method: "GET", headers, signal: AbortSignal.timeout(nextTimeout()) }, fetchDeps);
162
+ if (!res.ok)
163
+ return { status: "failed", detail: `guild ${guildId}: the command list read returned ${res.status}` };
164
+ held = await res.json();
165
+ }
166
+ catch (err) {
167
+ return { status: "failed", detail: `guild ${guildId}: the command list read failed (${err instanceof Error ? err.name : "error"})` };
168
+ }
169
+ const drift = discordCommandDrift(declared, held);
170
+ if (!drift.changed)
171
+ return { status: "unchanged", detail: `guild ${guildId}: ${drift.reason}` };
172
+ if (nextTimeout() <= 0) {
173
+ return { status: "failed", detail: `guild ${guildId}: ${drift.reason}, and the boot budget was spent before the write — retried on the next start` };
174
+ }
175
+ try {
176
+ // CREATE-OR-UPDATE BY NAME. Discord treats a POST naming an existing command as an update of
177
+ // that command, and touches nothing else at the scope — see the header on why the bulk PUT is
178
+ // wrong here.
179
+ const res = await discordFetch("POST /applications/{id}/guilds/{id}/commands", url, { method: "POST", headers, body: JSON.stringify(declared), signal: AbortSignal.timeout(nextTimeout()) }, fetchDeps);
180
+ if (!res.ok)
181
+ return { status: "failed", detail: `guild ${guildId}: ${drift.reason}, and the write returned ${res.status}` };
182
+ return { status: "updated", detail: `guild ${guildId}: ${drift.reason} — registered` };
183
+ }
184
+ catch (err) {
185
+ return { status: "failed", detail: `guild ${guildId}: ${drift.reason}, and the write failed (${err instanceof Error ? err.name : "error"})` };
186
+ }
187
+ }
188
+ /**
189
+ * THE RECONCILER the discord bundle registers with core's command plane.
190
+ *
191
+ * Runs at boot, so it is gated on BOTH deployment credentials being present: `DISCORD_APPLICATION_ID`
192
+ * says which app this is and `DISCORD_BOT_TOKEN` authenticates the call. Either missing ⇒ `skipped`,
193
+ * which is the correct reading of a deployment that carries the bundle but no Discord app — and it is
194
+ * also what keeps every offline test tier network-free, since no test publishes an application id.
195
+ *
196
+ * 🔴 THESE TWO STAY ENV. They are the credential pair this deployment speaks as, which is exactly the
197
+ * `secret`/`identity` half of the config-as-code line. The GUILD ids do not: they are tracked literals
198
+ * in `agent/commands/discord-commands.ts` and arrive here as the declared scope.
199
+ */
200
+ export async function reconcileDiscordCommands(scope, deps = {}) {
201
+ const applicationId = deps.applicationId ?? process.env.DISCORD_APPLICATION_ID;
202
+ const botToken = deps.botToken ?? process.env.DISCORD_BOT_TOKEN;
203
+ if (!applicationId || !botToken) {
204
+ return {
205
+ status: "skipped",
206
+ detail: "DISCORD_APPLICATION_ID and DISCORD_BOT_TOKEN must both be set for this deployment to register its own commands.",
207
+ };
208
+ }
209
+ const deadline = Date.now() + (deps.budgetMs ?? RECONCILE_BUDGET_MS);
210
+ const msLeft = () => deadline - Date.now();
211
+ const results = [];
212
+ for (const guildId of scope.scopes) {
213
+ results.push(await reconcileOneGuild(guildId, applicationId, botToken, deps, msLeft));
214
+ }
215
+ const detail = results.map((r) => r.detail).join("; ");
216
+ // A failure on ANY guild is the outcome, even when another guild succeeded: the boot log must not
217
+ // read green while one room is still missing the command.
218
+ if (results.some((r) => r.status === "failed"))
219
+ return { status: "failed", detail };
220
+ if (results.some((r) => r.status === "updated"))
221
+ return { status: "updated", detail };
222
+ return { status: "unchanged", detail };
223
+ }
224
+ //# sourceMappingURL=discord-command-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-command-sync.js","sourceRoot":"","sources":["../../../agent/commands/discord-command-sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { type AttachSessionForCancel, type OeCommand } from "@open-everywhere/core/agent/adapter-api.js";
2
+ import { type OpenEverywhereDiscordAuth } from "../channels/discord-identity.js";
3
+ import type { DiscordInteractionResponse } from "../approvals/discord-flow.js";
4
+ /**
5
+ * Walk Discord's nested SUB_COMMAND_GROUP/SUB_COMMAND option tree for "/oe" into an
6
+ * `OeCommand`. Discord's schema REQUIRES picking a concrete subcommand — you cannot invoke a
7
+ * bare "/oe budget" the way Slack's free-text grammar allows ("budget" alone means
8
+ * budget-status there); that's a genuine, documented platform difference (see
9
+ * docs/deploy/surfaces/discord.md), not a bug. Defensive: untrusted wire JSON, every field
10
+ * probed, NEVER throws.
11
+ */
12
+ export declare function parseDiscordOeCommandOptions(rawOptions: unknown): OeCommand;
13
+ interface ParsedOeInvocation {
14
+ readonly auth: OpenEverywhereDiscordAuth;
15
+ readonly command: OeCommand;
16
+ }
17
+ /**
18
+ * Parse a raw (already Ed25519-verified) Discord interaction body into the auth + command a
19
+ * "/oe" invocation carries — or an `{ error }` for a recognizably-ours-but-unusable body
20
+ * (no resolvable user, or a bot author), or `null` for anything that isn't type-2 name-"oe"
21
+ * at all (the front route's classifier already checked this; re-checking here is the same
22
+ * defensive belt-and-braces parseDiscordComponentClick applies to approval clicks). NEVER
23
+ * throws.
24
+ */
25
+ export declare function parseOeCommandInteraction(raw: unknown): ParsedOeInvocation | {
26
+ readonly error: string;
27
+ } | null;
28
+ /**
29
+ * Handle one verified Discord interaction the front route classified as "ours" (an "/oe"
30
+ * APPLICATION_COMMAND). Returns null for anything that doesn't actually parse as one (the
31
+ * caller then forwards to Eve — defensive fallback, mirrors handleDiscordApprovalInteraction);
32
+ * otherwise ALWAYS returns an ephemeral interaction response — NEVER throws, because Discord's
33
+ * interactions endpoint must always answer within 3s. Zero-inference: this never calls the
34
+ * model, and never consults spend caps (see core.ts's header — that's the entire point).
35
+ */
36
+ export declare function handleDiscordOeCommandInteraction(raw: unknown, attachSession?: AttachSessionForCancel): Promise<DiscordInteractionResponse | null>;
37
+ /**
38
+ * The Discord ApplicationCommand body for "/oe" — pure, no network, so the registration
39
+ * script (scripts/register-discord-commands.ts) and its test share exactly one definition.
40
+ * Shape: a "budget" SUB_COMMAND_GROUP (status/raise/clear) plus top-level "status"/"help"
41
+ * SUB_COMMANDs — see the file header for why "budget" alone isn't invokable on Discord.
42
+ */
43
+ export declare function buildOeDiscordCommandDefinition(): Record<string, unknown>;
44
+ export {};
45
+ //# sourceMappingURL=discord-commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-commands.d.ts","sourceRoot":"","sources":["../../../agent/commands/discord-commands.ts"],"names":[],"mappings":"AA2BA,OAAO,EAML,KAAK,sBAAsB,EAE3B,KAAK,SAAS,EACf,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAoB,KAAK,yBAAyB,EAAE,MAAM,iCAAiC,CAAC;AACnG,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;AA2D/E;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,OAAO,GAAG,SAAS,CA2C3E;AAED,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;CAC7B;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,kBAAkB,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAsB9G;AAED;;;;;;;GAOG;AACH,wBAAsB,iCAAiC,CACrD,GAAG,EAAE,OAAO,EACZ,aAAa,CAAC,EAAE,sBAAsB,GACrC,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC,CAqB5C;AAWD;;;;;GAKG;AACH,wBAAgB,+BAA+B,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsDzE"}
@@ -0,0 +1,266 @@
1
+ // The Discord I/O half of the "/oe" zero-inference slash command (backlog item 23) — the
2
+ // sibling of approvals/discord-flow.ts's split (pure render/parse in one file, the
3
+ // interactions-endpoint handler here). Two entry points:
4
+ //
5
+ // buildOeDiscordCommandDefinition() — the pure ApplicationCommand JSON body for "/oe"
6
+ // (SUB_COMMAND_GROUP "budget" + top-level "status"/
7
+ // "stop"/"rate"/"help"). Shared by the registration script
8
+ // AND its test, so there is exactly one source of truth
9
+ // for the shape Discord expects.
10
+ // handleDiscordOeCommandInteraction() — called by the Discord front route
11
+ // (channels/discord-front.ts) for every verified
12
+ // interaction body its classifier marked "oe-command".
13
+ // Returns null for anything that isn't actually a
14
+ // parseable "/oe" invocation (defensive belt-and-braces
15
+ // — the front's classifier already checked type===2 &&
16
+ // data.name==="oe"), so the caller falls back to
17
+ // forwarding rather than silently swallowing the click.
18
+ // NEVER throws — Discord's interactions endpoint must
19
+ // always answer within 3s, so every failure mode here
20
+ // is fail-soft (an ephemeral note).
21
+ //
22
+ // WHY THIS NEVER REACHES discordChannel's onCommand: docs/deploy/surfaces/discord.md notes
23
+ // that hook "does not filter on a specific command name — it accepts ANY registered
24
+ // application command" and dispatches a full agent turn. "/oe" is deliberately intercepted
25
+ // at the FRONT (before onCommand ever sees it) so it costs zero inference — see
26
+ // channels/discord-front.ts's classifyDiscordInteraction.
27
+ import { bindFrom, MODEL_AB_RATING_MAX, MODEL_AB_RATING_MIN, OE_COMMAND_NAME, runOeCommand, } from "@open-everywhere/core/agent/adapter-api.js";
28
+ import { buildDiscordAuth } from "../channels/discord-identity.js";
29
+ // Discord interaction-response wire constants, defined locally (same "leaf-over-import" call
30
+ // approvals/discord-flow.ts and approvals/discord-card.ts make for their own copies): importing
31
+ // eve's channel module here just for two stable public-API integers isn't worth the coupling.
32
+ const RESPONSE_CHANNEL_MESSAGE = 4; // CHANNEL_MESSAGE_WITH_SOURCE — a fresh reply to the invocation
33
+ const EPHEMERAL_FLAG = 64; // only the invoking user sees the reply
34
+ function ephemeral(content) {
35
+ return { type: RESPONSE_CHANNEL_MESSAGE, data: { content, flags: EPHEMERAL_FLAG } };
36
+ }
37
+ // --- defensive wire-JSON probing (untrusted body, post-signature-check) ---------------------
38
+ // Mirrors approvals/discord-flow.ts's parseDiscordComponentClick posture exactly: this is
39
+ // attacker-shaped JSON reaching a public URL (Discord relays whatever a client sends), so
40
+ // every field is probed, nothing assumed present or well-typed.
41
+ function isObject(v) {
42
+ return typeof v === "object" && v !== null;
43
+ }
44
+ function str(v) {
45
+ return typeof v === "string" && v.length > 0 ? v : undefined;
46
+ }
47
+ function num(v) {
48
+ if (typeof v === "number" && Number.isFinite(v))
49
+ return v;
50
+ if (typeof v === "string" && v.trim().length > 0) {
51
+ const n = Number(v);
52
+ return Number.isFinite(n) ? n : undefined;
53
+ }
54
+ return undefined;
55
+ }
56
+ /** Read Discord's raw numeric channel `type` off the interaction payload — duplicated in
57
+ * miniature from channels/discord.ts's private channelTypeFromRaw (not exported there; this
58
+ * file only needs the one field and importing the whole module for it isn't worth the
59
+ * coupling — the same leaf-duplication call the header note makes). */
60
+ function channelTypeFromRaw(raw) {
61
+ const ch = raw["channel"];
62
+ if (ch && typeof ch === "object") {
63
+ const t = ch["type"];
64
+ if (typeof t === "number")
65
+ return String(t);
66
+ }
67
+ return undefined;
68
+ }
69
+ function optArray(options) {
70
+ return Array.isArray(options) ? options : [];
71
+ }
72
+ function findOpt(options, name) {
73
+ return options.find((o) => isObject(o) && o.name === name);
74
+ }
75
+ /**
76
+ * Walk Discord's nested SUB_COMMAND_GROUP/SUB_COMMAND option tree for "/oe" into an
77
+ * `OeCommand`. Discord's schema REQUIRES picking a concrete subcommand — you cannot invoke a
78
+ * bare "/oe budget" the way Slack's free-text grammar allows ("budget" alone means
79
+ * budget-status there); that's a genuine, documented platform difference (see
80
+ * docs/deploy/surfaces/discord.md), not a bug. Defensive: untrusted wire JSON, every field
81
+ * probed, NEVER throws.
82
+ */
83
+ export function parseDiscordOeCommandOptions(rawOptions) {
84
+ const top = optArray(rawOptions);
85
+ if (top.length === 0)
86
+ return { kind: "help" }; // shouldn't happen (Discord enforces a subcommand) — safe default
87
+ const first = top[0];
88
+ const name = str(first.name);
89
+ if (name === "status")
90
+ return { kind: "status" };
91
+ if (name === "stop")
92
+ return { kind: "stop" };
93
+ if (name === "help")
94
+ return { kind: "help" };
95
+ if (name === "rate") {
96
+ // OPE-34. Discord types the option INTEGER with min/max, so a well-formed client cannot send an
97
+ // out-of-range value — but this is untrusted wire JSON, so the range is re-checked here and the
98
+ // message is the one a person reads. The BOUNDS COME FROM CORE, in all three places this file
99
+ // states them: a scale that lived here as a literal would silently start refusing scores the
100
+ // Slack parser accepts the day core's scale changed.
101
+ const scoreOpt = findOpt(optArray(first.options), "score");
102
+ const score = num(scoreOpt?.value);
103
+ if (score === undefined || !Number.isInteger(score) || score < MODEL_AB_RATING_MIN || score > MODEL_AB_RATING_MAX) {
104
+ return {
105
+ kind: "error",
106
+ message: `"/oe rate" needs a whole "score" from ${MODEL_AB_RATING_MIN} to ${MODEL_AB_RATING_MAX} (got ${JSON.stringify(scoreOpt?.value)}).`,
107
+ };
108
+ }
109
+ return { kind: "rate", score };
110
+ }
111
+ if (name === "budget") {
112
+ const sub = optArray(first.options)[0];
113
+ const subName = sub ? str(sub.name) : undefined;
114
+ if (!subName || subName === "status")
115
+ return { kind: "budget-status" };
116
+ if (subName === "clear")
117
+ return { kind: "budget-clear" };
118
+ if (subName === "raise") {
119
+ const usdOpt = findOpt(optArray(sub?.options), "usd");
120
+ const usd = num(usdOpt?.value);
121
+ if (usd === undefined || usd <= 0) {
122
+ return {
123
+ kind: "error",
124
+ message: `"/oe budget raise" needs a positive "usd" amount (got ${JSON.stringify(usdOpt?.value)}).`,
125
+ };
126
+ }
127
+ return { kind: "budget-raise", usd };
128
+ }
129
+ return { kind: "error", message: `Unknown "budget" subcommand "${subName}".` };
130
+ }
131
+ return { kind: "error", message: `Unknown "/oe" subcommand "${name ?? "?"}" — try "/oe help".` };
132
+ }
133
+ /**
134
+ * Parse a raw (already Ed25519-verified) Discord interaction body into the auth + command a
135
+ * "/oe" invocation carries — or an `{ error }` for a recognizably-ours-but-unusable body
136
+ * (no resolvable user, or a bot author), or `null` for anything that isn't type-2 name-"oe"
137
+ * at all (the front route's classifier already checked this; re-checking here is the same
138
+ * defensive belt-and-braces parseDiscordComponentClick applies to approval clicks). NEVER
139
+ * throws.
140
+ */
141
+ export function parseOeCommandInteraction(raw) {
142
+ if (!isObject(raw) || raw["type"] !== 2)
143
+ return null;
144
+ const data = isObject(raw["data"]) ? raw["data"] : undefined;
145
+ if (!data || data["name"] !== OE_COMMAND_NAME)
146
+ return null;
147
+ const member = isObject(raw["member"]) ? raw["member"] : undefined;
148
+ const memberUser = member && isObject(member["user"]) ? member["user"] : undefined;
149
+ const user = isObject(raw["user"]) ? raw["user"] : undefined;
150
+ const userId = (memberUser ? str(memberUser["id"]) : undefined) ?? (user ? str(user["id"]) : undefined);
151
+ const isBot = memberUser?.["bot"] === true || user?.["bot"] === true;
152
+ if (!userId || isBot) {
153
+ return { error: "Couldn't identify who invoked this — commands can't be run by a bot." };
154
+ }
155
+ const channelId = str(raw["channel_id"]);
156
+ if (!channelId) {
157
+ return { error: "Couldn't resolve this channel's id — try again from a real channel or DM." };
158
+ }
159
+ const guildId = str(raw["guild_id"]);
160
+ const channelType = channelTypeFromRaw(raw);
161
+ const auth = buildDiscordAuth({ userId, guildId, channelId, channelType });
162
+ const command = parseDiscordOeCommandOptions(data["options"]);
163
+ return { auth, command };
164
+ }
165
+ /**
166
+ * Handle one verified Discord interaction the front route classified as "ours" (an "/oe"
167
+ * APPLICATION_COMMAND). Returns null for anything that doesn't actually parse as one (the
168
+ * caller then forwards to Eve — defensive fallback, mirrors handleDiscordApprovalInteraction);
169
+ * otherwise ALWAYS returns an ephemeral interaction response — NEVER throws, because Discord's
170
+ * interactions endpoint must always answer within 3s. Zero-inference: this never calls the
171
+ * model, and never consults spend caps (see core.ts's header — that's the entire point).
172
+ */
173
+ export async function handleDiscordOeCommandInteraction(raw, attachSession) {
174
+ const parsed = parseOeCommandInteraction(raw);
175
+ if (parsed === null)
176
+ return null; // not actually an "/oe" invocation — forward to Eve
177
+ if ("error" in parsed)
178
+ return ephemeral(parsed.error);
179
+ try {
180
+ const ctx = { channel: { kind: "discord" }, session: { auth: { current: parsed.auth } } };
181
+ const binding = bindFrom(ctx);
182
+ // `attachSession` is eve's I/O-free cancel door, threaded down from the route args purely for
183
+ // "/oe stop" (OPE-418). Every other branch ignores it, and its ABSENCE is handled with a
184
+ // user-facing sentence rather than a silent no-op — see core.ts's runStop.
185
+ const text = await runOeCommand(parsed.command, binding, attachSession ? { attachSession } : {});
186
+ return ephemeral(text);
187
+ }
188
+ catch (err) {
189
+ // runOeCommand's budget-raise/clear branches can throw a user-facing validation error
190
+ // (e.g. "propose from a DM needs an explicit scope") — surface it verbatim; anything else
191
+ // (a genuinely unexpected store/network failure) gets a generic fail-soft note. Either way,
192
+ // never throw out of this function.
193
+ console.error(`[open-everywhere:discord-commands] "/oe" command failed softly: ${String(err)}`);
194
+ return ephemeral(`Couldn't complete that: ${err instanceof Error ? err.message : String(err)}`);
195
+ }
196
+ }
197
+ // --- registration: the pure ApplicationCommand JSON body ------------------------------------
198
+ // Discord option type integers (stable public API constants, defined locally — see the header
199
+ // note on why this file doesn't import eve's channel module for two integers).
200
+ const SUB_COMMAND = 1;
201
+ const SUB_COMMAND_GROUP = 2;
202
+ const OPTION_INTEGER = 4;
203
+ const OPTION_NUMBER = 10;
204
+ const CHAT_INPUT = 1;
205
+ /**
206
+ * The Discord ApplicationCommand body for "/oe" — pure, no network, so the registration
207
+ * script (scripts/register-discord-commands.ts) and its test share exactly one definition.
208
+ * Shape: a "budget" SUB_COMMAND_GROUP (status/raise/clear) plus top-level "status"/"help"
209
+ * SUB_COMMANDs — see the file header for why "budget" alone isn't invokable on Discord.
210
+ */
211
+ export function buildOeDiscordCommandDefinition() {
212
+ return {
213
+ name: OE_COMMAND_NAME,
214
+ type: CHAT_INPUT,
215
+ description: "Open Everywhere control-plane commands (status, budget) — no model turn.",
216
+ options: [
217
+ {
218
+ name: "budget",
219
+ description: "Spend-cap status and proposals for this channel/server.",
220
+ type: SUB_COMMAND_GROUP,
221
+ options: [
222
+ { name: "status", description: "Show effective caps and current-month spend.", type: SUB_COMMAND },
223
+ {
224
+ name: "raise",
225
+ description: "Propose a new monthly USD cap for this channel (posts an approval card).",
226
+ type: SUB_COMMAND,
227
+ options: [
228
+ {
229
+ name: "usd",
230
+ description: "Proposed monthly USD budget.",
231
+ type: OPTION_NUMBER,
232
+ required: true,
233
+ min_value: 0.01,
234
+ },
235
+ ],
236
+ },
237
+ { name: "clear", description: "Propose clearing this channel's spend-limit override.", type: SUB_COMMAND },
238
+ ],
239
+ },
240
+ { name: "status", description: "Deploy sanity: app version, model config, memory provider.", type: SUB_COMMAND },
241
+ // OPE-418: the free, pre-model stop. A top-level SUB_COMMAND (not a group) because it takes
242
+ // no arguments — the channel it was invoked in IS the argument.
243
+ { name: "stop", description: "Stand down the turn running in this channel.", type: SUB_COMMAND },
244
+ // OPE-34: the human mark on the model A/B test. A top-level SUB_COMMAND with one required
245
+ // INTEGER, bounded by Discord's own min/max so the client refuses an out-of-range score
246
+ // before it ever reaches the interaction endpoint.
247
+ {
248
+ name: "rate",
249
+ description: `Rate this channel's last answer (${MODEL_AB_RATING_MIN}-${MODEL_AB_RATING_MAX}) to mark the model A/B test.`,
250
+ type: SUB_COMMAND,
251
+ options: [
252
+ {
253
+ name: "score",
254
+ description: `${MODEL_AB_RATING_MIN} (worst) to ${MODEL_AB_RATING_MAX} (best).`,
255
+ type: OPTION_INTEGER,
256
+ required: true,
257
+ min_value: MODEL_AB_RATING_MIN,
258
+ max_value: MODEL_AB_RATING_MAX,
259
+ },
260
+ ],
261
+ },
262
+ { name: "help", description: "List the /oe commands.", type: SUB_COMMAND },
263
+ ],
264
+ };
265
+ }
266
+ //# sourceMappingURL=discord-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discord-commands.js","sourceRoot":"","sources":["../../../agent/commands/discord-commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export declare const CONTRIBUTIONS: {
2
+ readonly schemaVersion: 1;
3
+ readonly package: "@open-everywhere/provider-discord";
4
+ readonly register: "agent/register.js#registerDiscordProvider";
5
+ readonly contributions: readonly [{
6
+ readonly kind: "channel";
7
+ readonly name: "discord";
8
+ readonly module: "agent/channels/discord.js";
9
+ readonly summary: "The Discord surface. Wires Discord's HTTP Interactions (slash commands) → an OpenEverywhere session, and — the point of the \"Discord-binding\" slice — populates the session auth context with the REAL…";
10
+ }, {
11
+ readonly kind: "channel";
12
+ readonly name: "discord-gateway";
13
+ readonly module: "agent/channels/discord-gateway.js";
14
+ readonly summary: "The Discord GATEWAY ingestion surface — Milestone 2 of the Discord Gateway feature.";
15
+ }];
16
+ };
17
+ //# sourceMappingURL=contributions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contributions.d.ts","sourceRoot":"","sources":["../../agent/contributions.ts"],"names":[],"mappings":"AAsBA,eAAO,MAAM,aAAa;aACxB,aAAa,EAAE,CAAC;aAChB,OAAO,EAAE,mCAAmC;aAC5C,QAAQ,EAAE,2CAA2C;aACrD,aAAa;iBACT,IAAI,EAAE,SAAS;iBAAE,IAAI,EAAE,SAAS;iBAAE,MAAM,EAAE,2BAA2B;iBAAE,OAAO,EAAE,2MAA2M;;iBAC3R,IAAI,EAAE,SAAS;iBAAE,IAAI,EAAE,iBAAiB;iBAAE,MAAM,EAAE,mCAAmC;iBAAE,OAAO,EAAE,qFAAqF;;CAEjL,CAAC"}
@@ -0,0 +1,31 @@
1
+ // THE CONTRIBUTION MANIFEST for `@open-everywhere/provider-discord` (OPE-375) — the curated
2
+ // catalogue of everything this package OFFERS an app's eve slot tree. `oe build` reads it;
3
+ // the app's `roster.config.ts` decides about it, and `roster.lock.json` records the review.
4
+ //
5
+ // 🔴 CURATED, NOT SCANNED, AND THAT IS MEASURED RATHER THAN STYLISTIC. A directory scan cannot
6
+ // enumerate contributions: core ships 47 files under `agent/channels/` of which 9 are channels, and
7
+ // 13 under `agent/connections/` of which 1 is a connection. Shape predicates do not rescue it either
8
+ // — only 8 of the 9 mounted core channels contain `defineChannel(`. A hand-kept list is the only
9
+ // honest catalogue, so keeping it current is a real authoring duty (`npm run lint:contributions`
10
+ // scan-compares the two kinds where a predicate IS sound — tools and hooks — and says plainly that
11
+ // the other four are unguarded).
12
+ //
13
+ // 🔴 THIS FILE IMPORTS NOTHING, DELIBERATELY. A provider bundle may reach core only through the
14
+ // `@open-everywhere/core/agent/adapter-api.js` barrel (the boundary lock, packages/core/scripts/
15
+ // boundary-test.ts), so a manifest that imported its own types would be a boundary red — and
16
+ // widening the barrel to admit a build-time type would be the wrong fix. Every manifest is therefore
17
+ // import-free DATA whose shape `roster-plan.ts` validates at build time, exactly as it must for a
18
+ // third-party package it has never seen. The types are in
19
+ // `@open-everywhere/core/agent/scaffold/roster-config.js` for READERS.
20
+ //
21
+ // ERASABLE SYNTAX ONLY (no `enum`, no `namespace`) — `oe build` loads this with a bare import.
22
+ export const CONTRIBUTIONS = {
23
+ schemaVersion: 1,
24
+ package: "@open-everywhere/provider-discord",
25
+ register: "agent/register.js#registerDiscordProvider",
26
+ contributions: [
27
+ { kind: "channel", name: "discord", module: "agent/channels/discord.js", summary: "The Discord surface. Wires Discord's HTTP Interactions (slash commands) → an OpenEverywhere session, and — the point of the \"Discord-binding\" slice — populates the session auth context with the REAL…" },
28
+ { kind: "channel", name: "discord-gateway", module: "agent/channels/discord-gateway.js", summary: "The Discord GATEWAY ingestion surface — Milestone 2 of the Discord Gateway feature." },
29
+ ],
30
+ };
31
+ //# sourceMappingURL=contributions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contributions.js","sourceRoot":"","sources":["../../agent/contributions.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,2FAA2F;AAC3F,4FAA4F;AAC5F,EAAE;AACF,+FAA+F;AAC/F,oGAAoG;AACpG,qGAAqG;AACrG,iGAAiG;AACjG,iGAAiG;AACjG,mGAAmG;AACnG,iCAAiC;AACjC,EAAE;AACF,gGAAgG;AAChG,iGAAiG;AACjG,6FAA6F;AAC7F,qGAAqG;AACrG,kGAAkG;AAClG,0DAA0D;AAC1D,uEAAuE;AACvE,EAAE;AACF,+FAA+F;AAE/F,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,aAAa,EAAE,CAAC;IAChB,OAAO,EAAE,mCAAmC;IAC5C,QAAQ,EAAE,2CAA2C;IACrD,aAAa,EAAE;QACb,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,2BAA2B,EAAE,OAAO,EAAE,2MAA2M,EAAE;QAC/R,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,mCAAmC,EAAE,OAAO,EAAE,qFAAqF,EAAE;KAC1L;CACO,CAAC"}