@spinabot/brigade 1.5.0 → 1.6.1

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 (195) hide show
  1. package/dist/agents/agent-loop.d.ts.map +1 -1
  2. package/dist/agents/agent-loop.js +51 -1
  3. package/dist/agents/agent-loop.js.map +1 -1
  4. package/dist/agents/channels/bundled-channel-metas.d.ts +4 -0
  5. package/dist/agents/channels/bundled-channel-metas.d.ts.map +1 -1
  6. package/dist/agents/channels/bundled-channel-metas.js +22 -0
  7. package/dist/agents/channels/bundled-channel-metas.js.map +1 -1
  8. package/dist/agents/channels/discord/account-config.d.ts +117 -0
  9. package/dist/agents/channels/discord/account-config.d.ts.map +1 -0
  10. package/dist/agents/channels/discord/account-config.js +260 -0
  11. package/dist/agents/channels/discord/account-config.js.map +1 -0
  12. package/dist/agents/channels/discord/adapter.d.ts +56 -0
  13. package/dist/agents/channels/discord/adapter.d.ts.map +1 -0
  14. package/dist/agents/channels/discord/adapter.js +526 -0
  15. package/dist/agents/channels/discord/adapter.js.map +1 -0
  16. package/dist/agents/channels/discord/approval-authorize.d.ts +43 -0
  17. package/dist/agents/channels/discord/approval-authorize.d.ts.map +1 -0
  18. package/dist/agents/channels/discord/approval-authorize.js +71 -0
  19. package/dist/agents/channels/discord/approval-authorize.js.map +1 -0
  20. package/dist/agents/channels/discord/approval-native.d.ts +68 -0
  21. package/dist/agents/channels/discord/approval-native.d.ts.map +1 -0
  22. package/dist/agents/channels/discord/approval-native.js +81 -0
  23. package/dist/agents/channels/discord/approval-native.js.map +1 -0
  24. package/dist/agents/channels/discord/command-menu.d.ts +49 -0
  25. package/dist/agents/channels/discord/command-menu.d.ts.map +1 -0
  26. package/dist/agents/channels/discord/command-menu.js +73 -0
  27. package/dist/agents/channels/discord/command-menu.js.map +1 -0
  28. package/dist/agents/channels/discord/components.d.ts +97 -0
  29. package/dist/agents/channels/discord/components.d.ts.map +1 -0
  30. package/dist/agents/channels/discord/components.js +131 -0
  31. package/dist/agents/channels/discord/components.js.map +1 -0
  32. package/dist/agents/channels/discord/connection.d.ts +387 -0
  33. package/dist/agents/channels/discord/connection.d.ts.map +1 -0
  34. package/dist/agents/channels/discord/connection.js +786 -0
  35. package/dist/agents/channels/discord/connection.js.map +1 -0
  36. package/dist/agents/channels/discord/draft-stream.d.ts +92 -0
  37. package/dist/agents/channels/discord/draft-stream.d.ts.map +1 -0
  38. package/dist/agents/channels/discord/draft-stream.js +213 -0
  39. package/dist/agents/channels/discord/draft-stream.js.map +1 -0
  40. package/dist/agents/channels/discord/format.d.ts +55 -0
  41. package/dist/agents/channels/discord/format.d.ts.map +1 -0
  42. package/dist/agents/channels/discord/format.js +247 -0
  43. package/dist/agents/channels/discord/format.js.map +1 -0
  44. package/dist/agents/channels/discord/inbound-extras.d.ts +220 -0
  45. package/dist/agents/channels/discord/inbound-extras.d.ts.map +1 -0
  46. package/dist/agents/channels/discord/inbound-extras.js +351 -0
  47. package/dist/agents/channels/discord/inbound-extras.js.map +1 -0
  48. package/dist/agents/channels/discord/index.d.ts +14 -0
  49. package/dist/agents/channels/discord/index.d.ts.map +1 -0
  50. package/dist/agents/channels/discord/index.js +14 -0
  51. package/dist/agents/channels/discord/index.js.map +1 -0
  52. package/dist/agents/channels/discord/media.d.ts +85 -0
  53. package/dist/agents/channels/discord/media.d.ts.map +1 -0
  54. package/dist/agents/channels/discord/media.js +242 -0
  55. package/dist/agents/channels/discord/media.js.map +1 -0
  56. package/dist/agents/channels/discord/module.d.ts +15 -0
  57. package/dist/agents/channels/discord/module.d.ts.map +1 -0
  58. package/dist/agents/channels/discord/module.js +22 -0
  59. package/dist/agents/channels/discord/module.js.map +1 -0
  60. package/dist/agents/channels/discord/plugin.d.ts +73 -0
  61. package/dist/agents/channels/discord/plugin.d.ts.map +1 -0
  62. package/dist/agents/channels/discord/plugin.js +303 -0
  63. package/dist/agents/channels/discord/plugin.js.map +1 -0
  64. package/dist/agents/channels/discord/probe.d.ts +93 -0
  65. package/dist/agents/channels/discord/probe.d.ts.map +1 -0
  66. package/dist/agents/channels/discord/probe.js +158 -0
  67. package/dist/agents/channels/discord/probe.js.map +1 -0
  68. package/dist/agents/channels/discord/reasoning-lane.d.ts +42 -0
  69. package/dist/agents/channels/discord/reasoning-lane.d.ts.map +1 -0
  70. package/dist/agents/channels/discord/reasoning-lane.js +68 -0
  71. package/dist/agents/channels/discord/reasoning-lane.js.map +1 -0
  72. package/dist/agents/channels/inbound-pipeline.d.ts.map +1 -1
  73. package/dist/agents/channels/inbound-pipeline.js +65 -7
  74. package/dist/agents/channels/inbound-pipeline.js.map +1 -1
  75. package/dist/agents/channels/manager.d.ts.map +1 -1
  76. package/dist/agents/channels/manager.js +18 -0
  77. package/dist/agents/channels/manager.js.map +1 -1
  78. package/dist/agents/channels/sdk.d.ts +4 -0
  79. package/dist/agents/channels/sdk.d.ts.map +1 -1
  80. package/dist/agents/channels/sdk.js +4 -0
  81. package/dist/agents/channels/sdk.js.map +1 -1
  82. package/dist/agents/channels/slack/account-config.d.ts +172 -0
  83. package/dist/agents/channels/slack/account-config.d.ts.map +1 -0
  84. package/dist/agents/channels/slack/account-config.js +353 -0
  85. package/dist/agents/channels/slack/account-config.js.map +1 -0
  86. package/dist/agents/channels/slack/account-registry.d.ts +45 -0
  87. package/dist/agents/channels/slack/account-registry.d.ts.map +1 -0
  88. package/dist/agents/channels/slack/account-registry.js +58 -0
  89. package/dist/agents/channels/slack/account-registry.js.map +1 -0
  90. package/dist/agents/channels/slack/adapter.d.ts +66 -0
  91. package/dist/agents/channels/slack/adapter.d.ts.map +1 -0
  92. package/dist/agents/channels/slack/adapter.js +547 -0
  93. package/dist/agents/channels/slack/adapter.js.map +1 -0
  94. package/dist/agents/channels/slack/approval-authorize.d.ts +43 -0
  95. package/dist/agents/channels/slack/approval-authorize.d.ts.map +1 -0
  96. package/dist/agents/channels/slack/approval-authorize.js +71 -0
  97. package/dist/agents/channels/slack/approval-authorize.js.map +1 -0
  98. package/dist/agents/channels/slack/approval-native.d.ts +70 -0
  99. package/dist/agents/channels/slack/approval-native.d.ts.map +1 -0
  100. package/dist/agents/channels/slack/approval-native.js +85 -0
  101. package/dist/agents/channels/slack/approval-native.js.map +1 -0
  102. package/dist/agents/channels/slack/blocks.d.ts +125 -0
  103. package/dist/agents/channels/slack/blocks.d.ts.map +1 -0
  104. package/dist/agents/channels/slack/blocks.js +145 -0
  105. package/dist/agents/channels/slack/blocks.js.map +1 -0
  106. package/dist/agents/channels/slack/command-menu.d.ts +44 -0
  107. package/dist/agents/channels/slack/command-menu.d.ts.map +1 -0
  108. package/dist/agents/channels/slack/command-menu.js +66 -0
  109. package/dist/agents/channels/slack/command-menu.js.map +1 -0
  110. package/dist/agents/channels/slack/connection.d.ts +422 -0
  111. package/dist/agents/channels/slack/connection.d.ts.map +1 -0
  112. package/dist/agents/channels/slack/connection.js +1042 -0
  113. package/dist/agents/channels/slack/connection.js.map +1 -0
  114. package/dist/agents/channels/slack/directory-live.d.ts +129 -0
  115. package/dist/agents/channels/slack/directory-live.d.ts.map +1 -0
  116. package/dist/agents/channels/slack/directory-live.js +148 -0
  117. package/dist/agents/channels/slack/directory-live.js.map +1 -0
  118. package/dist/agents/channels/slack/draft-stream.d.ts +93 -0
  119. package/dist/agents/channels/slack/draft-stream.d.ts.map +1 -0
  120. package/dist/agents/channels/slack/draft-stream.js +218 -0
  121. package/dist/agents/channels/slack/draft-stream.js.map +1 -0
  122. package/dist/agents/channels/slack/format.d.ts +41 -0
  123. package/dist/agents/channels/slack/format.d.ts.map +1 -0
  124. package/dist/agents/channels/slack/format.js +271 -0
  125. package/dist/agents/channels/slack/format.js.map +1 -0
  126. package/dist/agents/channels/slack/inbound-extras.d.ts +179 -0
  127. package/dist/agents/channels/slack/inbound-extras.d.ts.map +1 -0
  128. package/dist/agents/channels/slack/inbound-extras.js +257 -0
  129. package/dist/agents/channels/slack/inbound-extras.js.map +1 -0
  130. package/dist/agents/channels/slack/index.d.ts +15 -0
  131. package/dist/agents/channels/slack/index.d.ts.map +1 -0
  132. package/dist/agents/channels/slack/index.js +15 -0
  133. package/dist/agents/channels/slack/index.js.map +1 -0
  134. package/dist/agents/channels/slack/media.d.ts +90 -0
  135. package/dist/agents/channels/slack/media.d.ts.map +1 -0
  136. package/dist/agents/channels/slack/media.js +215 -0
  137. package/dist/agents/channels/slack/media.js.map +1 -0
  138. package/dist/agents/channels/slack/module.d.ts +26 -0
  139. package/dist/agents/channels/slack/module.d.ts.map +1 -0
  140. package/dist/agents/channels/slack/module.js +67 -0
  141. package/dist/agents/channels/slack/module.js.map +1 -0
  142. package/dist/agents/channels/slack/plugin.d.ts +69 -0
  143. package/dist/agents/channels/slack/plugin.d.ts.map +1 -0
  144. package/dist/agents/channels/slack/plugin.js +318 -0
  145. package/dist/agents/channels/slack/plugin.js.map +1 -0
  146. package/dist/agents/channels/slack/probe.d.ts +72 -0
  147. package/dist/agents/channels/slack/probe.d.ts.map +1 -0
  148. package/dist/agents/channels/slack/probe.js +103 -0
  149. package/dist/agents/channels/slack/probe.js.map +1 -0
  150. package/dist/agents/channels/slack/proxy-agent.d.ts +30 -0
  151. package/dist/agents/channels/slack/proxy-agent.d.ts.map +1 -0
  152. package/dist/agents/channels/slack/proxy-agent.js +44 -0
  153. package/dist/agents/channels/slack/proxy-agent.js.map +1 -0
  154. package/dist/agents/channels/slack/reasoning-lane.d.ts +42 -0
  155. package/dist/agents/channels/slack/reasoning-lane.d.ts.map +1 -0
  156. package/dist/agents/channels/slack/reasoning-lane.js +68 -0
  157. package/dist/agents/channels/slack/reasoning-lane.js.map +1 -0
  158. package/dist/agents/channels/slack/user-directory.d.ts +69 -0
  159. package/dist/agents/channels/slack/user-directory.d.ts.map +1 -0
  160. package/dist/agents/channels/slack/user-directory.js +94 -0
  161. package/dist/agents/channels/slack/user-directory.js.map +1 -0
  162. package/dist/agents/channels/slack/webhook.d.ts +89 -0
  163. package/dist/agents/channels/slack/webhook.d.ts.map +1 -0
  164. package/dist/agents/channels/slack/webhook.js +228 -0
  165. package/dist/agents/channels/slack/webhook.js.map +1 -0
  166. package/dist/agents/channels/telegram/format.d.ts.map +1 -1
  167. package/dist/agents/channels/telegram/format.js +17 -1
  168. package/dist/agents/channels/telegram/format.js.map +1 -1
  169. package/dist/agents/channels/telegram/webhook.d.ts.map +1 -1
  170. package/dist/agents/channels/telegram/webhook.js +7 -1
  171. package/dist/agents/channels/telegram/webhook.js.map +1 -1
  172. package/dist/agents/extensions/modules/index.d.ts.map +1 -1
  173. package/dist/agents/extensions/modules/index.js +10 -0
  174. package/dist/agents/extensions/modules/index.js.map +1 -1
  175. package/dist/agents/tools/cron-tool.d.ts.map +1 -1
  176. package/dist/agents/tools/cron-tool.js +4 -1
  177. package/dist/agents/tools/cron-tool.js.map +1 -1
  178. package/dist/buildstamp.json +1 -1
  179. package/dist/core/auth-bridge.d.ts +1 -0
  180. package/dist/core/auth-bridge.d.ts.map +1 -1
  181. package/dist/core/auth-bridge.js +46 -1
  182. package/dist/core/auth-bridge.js.map +1 -1
  183. package/dist/core/server.d.ts.map +1 -1
  184. package/dist/core/server.js +40 -5
  185. package/dist/core/server.js.map +1 -1
  186. package/dist/cron/isolated-agent/run-executor.d.ts +11 -0
  187. package/dist/cron/isolated-agent/run-executor.d.ts.map +1 -1
  188. package/dist/cron/isolated-agent/run-executor.js +20 -4
  189. package/dist/cron/isolated-agent/run-executor.js.map +1 -1
  190. package/dist/cron/types.d.ts +8 -0
  191. package/dist/cron/types.d.ts.map +1 -1
  192. package/dist/system-prompt/assembler.d.ts.map +1 -1
  193. package/dist/system-prompt/assembler.js +4 -2
  194. package/dist/system-prompt/assembler.js.map +1 -1
  195. package/package.json +5 -1
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Discord inline-approval authorization.
3
+ *
4
+ * When an approval prompt is rendered as buttons, ANY member who can see the
5
+ * message could press a button. Brigade's central inbound pipeline already runs
6
+ * the access-control gate before a button press reaches the approval-callback
7
+ * path, so only an admitted (allow-listed / owner) peer gets here at all — but a
8
+ * SHARED Discord guild channel is the edge case: in a channel the bot is in, an
9
+ * admitted member's button press should still be allowed only when that presser
10
+ * is an approved approver, not merely present in the room. Discord's
11
+ * multi-member guilds make this gate more load-bearing than a 1:1 DM.
12
+ *
13
+ * This predicate is the channel's `approvalCapability.authorizeApprover`. It is
14
+ * invoked CENTRALLY by `tryConsumeChannelApprovalCallback` with the presser's
15
+ * `senderId` (the Discord user id, a snowflake); returning `{ authorized: false,
16
+ * reason }` refuses the press without consuming the operator's pending approval
17
+ * (so the real operator can still answer). Policy:
18
+ *
19
+ * - When the channel has an explicit allow-from list configured (the approved
20
+ * senders), only those ids may approve.
21
+ * - When NO allow-from list is configured, defer to the access gate that
22
+ * already admitted the inbound and authorize the press (matches the text-
23
+ * reply path, which has no extra approver gate).
24
+ *
25
+ * Pure + deterministic over its `cfg` + `senderId` inputs — no I/O. Discord
26
+ * mirror of `slack/approval-authorize.ts`.
27
+ */
28
+ /** Read the channel's configured allow-from sender ids (string-normalized). */
29
+ function configuredAllowFrom(cfg, accountId) {
30
+ const channels = cfg.channels;
31
+ const slot = channels?.discord;
32
+ if (!slot)
33
+ return [];
34
+ const ids = [];
35
+ const push = (list) => {
36
+ for (const v of list ?? []) {
37
+ const s = String(v).trim();
38
+ if (s)
39
+ ids.push(s);
40
+ }
41
+ };
42
+ push(slot.allowFrom);
43
+ // Per-account allow-from (multi-account shape) when an accountId is supplied.
44
+ if (accountId && Array.isArray(slot.accounts)) {
45
+ for (const entry of slot.accounts) {
46
+ if (entry && typeof entry.id === "string" && entry.id.trim() === accountId.trim())
47
+ push(entry.allowFrom);
48
+ }
49
+ }
50
+ return ids;
51
+ }
52
+ /**
53
+ * Resolve whether `senderId` is allowed to answer a Discord inline approval.
54
+ * Returns `{ authorized: true }` when no explicit allow-from gate applies (the
55
+ * central access gate already admitted the inbound), or when the presser is on
56
+ * the configured allow-from list. Otherwise refuses with a reason.
57
+ */
58
+ export function resolveDiscordApprover(args) {
59
+ const allow = configuredAllowFrom(args.cfg, args.accountId);
60
+ // No explicit allow list → defer to the access gate that already ran.
61
+ if (allow.length === 0)
62
+ return { authorized: true };
63
+ const sender = (args.senderId ?? "").trim();
64
+ if (sender && allow.includes(sender))
65
+ return { authorized: true };
66
+ return {
67
+ authorized: false,
68
+ reason: "Only an approved sender can answer that approval.",
69
+ };
70
+ }
71
+ //# sourceMappingURL=approval-authorize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-authorize.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/approval-authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAIH,+EAA+E;AAC/E,SAAS,mBAAmB,CAAC,GAAkB,EAAE,SAAkB;IAClE,MAAM,QAAQ,GAAI,GAA8C,CAAC,QAAQ,CAAC;IAC1E,MAAM,IAAI,GAAG,QAAQ,EAAE,OAEX,CAAC;IACb,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,CAAC,IAA6B,EAAE,EAAE;QAC9C,KAAK,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;YAC5B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAC3B,IAAI,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACF,CAAC,CAAC;IACF,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACrB,8EAA8E;IAC9E,IAAI,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,QAAQ,IAAI,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,SAAS,CAAC,IAAI,EAAE;gBAAE,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC1G,CAAC;IACF,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAItC;IACA,MAAM,KAAK,GAAG,mBAAmB,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC5D,sEAAsE;IACtE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IACpD,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5C,IAAI,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IAClE,OAAO;QACN,UAAU,EAAE,KAAK;QACjB,MAAM,EAAE,mDAAmD;KAC3D,CAAC;AACH,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Discord message-component rendering for native approval prompts.
3
+ *
4
+ * When a channel-routed turn raises an exec/plugin approval AND the Discord
5
+ * adapter has opted into `approvalCapability.sendApprovalPrompt`, the central
6
+ * approval-router asks this channel to render the question as native BUTTONS
7
+ * instead of the default "reply yes/no" text card. The button payloads are
8
+ * produced by the CENTRAL codec (`buildApprovalCallbackButtons`) so the press
9
+ * comes back as an `InboundMessage.callbackQuery` the central
10
+ * `tryConsumeChannelApprovalCallback` decodes + resolves — this file only maps
11
+ * the codec's `{ label, data }` specs onto Discord's component-row shape (via
12
+ * `components.ts`) and assembles the prompt text.
13
+ *
14
+ * SAFETY: every button `custom_id` here is the codec's output — versioned,
15
+ * base64url + printable-ASCII, already proven `<= 64` UTF-8 bytes by
16
+ * `encodeApprovalCallback` (and well under Discord's 100-char custom_id cap).
17
+ * This module never mints its own payloads.
18
+ *
19
+ * Discord mirror of `slack/approval-native.ts`.
20
+ */
21
+ import { type DiscordActionRow } from "./components.js";
22
+ /** The Discord message payload an approval prompt sends: prompt `text` + button `rows`. */
23
+ export interface DiscordApprovalMessage {
24
+ /** Prompt body rendered above the buttons. */
25
+ text: string;
26
+ /** The component rows (a row of up to 5 buttons; up to 5 rows). */
27
+ rows: DiscordActionRow[];
28
+ }
29
+ /**
30
+ * Build the component approval message for an approval prompt from the central
31
+ * codec. Returns `null` when fewer than two byte-safe buttons could be minted (a
32
+ * pathologically long approval id) — the caller then falls back to the text
33
+ * prompt rather than ship a half-rendered prompt.
34
+ *
35
+ * `allowAlways: false` drops the "Allow always" button (approvals where
36
+ * persisting an allowlist entry doesn't apply).
37
+ */
38
+ export declare function buildDiscordApprovalMessage(args: {
39
+ approvalId: string;
40
+ command: string;
41
+ approvalKind: "exec" | "plugin";
42
+ toolName?: string;
43
+ allowAlways?: boolean;
44
+ }): DiscordApprovalMessage | null;
45
+ /**
46
+ * Compose the operator-facing approval question text rendered ABOVE the buttons.
47
+ * Kept short + control-char-scrubbed; the buttons carry the action, so the text
48
+ * only needs the command preview + a one-line ask. The 🦁 mark is the Brigade
49
+ * brand-stamp so the operator recognises this as a Brigade prompt. The command
50
+ * preview is wrapped in a Discord inline code span (single backticks) so it
51
+ * renders verbatim.
52
+ */
53
+ export declare function buildDiscordApprovalText(args: {
54
+ command: string;
55
+ approvalKind: "exec" | "plugin";
56
+ toolName?: string;
57
+ agentId?: string;
58
+ }): string;
59
+ /**
60
+ * Parse a pressed button's `custom_id` into the pending-approval id + decision,
61
+ * via the CENTRAL codec. Returns `null` when the press wasn't an approval button
62
+ * (a general button, or a foreign id) so the caller can fall through.
63
+ */
64
+ export declare function parseDiscordApprovalAction(customId: string | undefined): {
65
+ approvalId: string;
66
+ decision: "allow-once" | "allow-always" | "deny";
67
+ } | null;
68
+ //# sourceMappingURL=approval-native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-native.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/approval-native.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAA4B,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAKlF,2FAA2F;AAC3F,MAAM,WAAW,sBAAsB;IACtC,8CAA8C;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,IAAI,EAAE,gBAAgB,EAAE,CAAC;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,QAAQ,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;CACtB,GAAG,sBAAsB,GAAG,IAAI,CAchC;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,GAAG,QAAQ,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,MAAM,CAWT;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACzC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC1B;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,GAAG,cAAc,GAAG,MAAM,CAAA;CAAE,GAAG,IAAI,CAGjF"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Discord message-component rendering for native approval prompts.
3
+ *
4
+ * When a channel-routed turn raises an exec/plugin approval AND the Discord
5
+ * adapter has opted into `approvalCapability.sendApprovalPrompt`, the central
6
+ * approval-router asks this channel to render the question as native BUTTONS
7
+ * instead of the default "reply yes/no" text card. The button payloads are
8
+ * produced by the CENTRAL codec (`buildApprovalCallbackButtons`) so the press
9
+ * comes back as an `InboundMessage.callbackQuery` the central
10
+ * `tryConsumeChannelApprovalCallback` decodes + resolves — this file only maps
11
+ * the codec's `{ label, data }` specs onto Discord's component-row shape (via
12
+ * `components.ts`) and assembles the prompt text.
13
+ *
14
+ * SAFETY: every button `custom_id` here is the codec's output — versioned,
15
+ * base64url + printable-ASCII, already proven `<= 64` UTF-8 bytes by
16
+ * `encodeApprovalCallback` (and well under Discord's 100-char custom_id cap).
17
+ * This module never mints its own payloads.
18
+ *
19
+ * Discord mirror of `slack/approval-native.ts`.
20
+ */
21
+ import { buildApprovalCallbackButtons, decodeApprovalCallback } from "../sdk.js";
22
+ import { buildDiscordApprovalRows } from "./components.js";
23
+ /** C0/C1 control-character class (incl. NUL) — never a raw control byte in source. */
24
+ const CONTROL_CHARS_RE = /[\x00-\x1f\x7f-\x9f]/g;
25
+ /**
26
+ * Build the component approval message for an approval prompt from the central
27
+ * codec. Returns `null` when fewer than two byte-safe buttons could be minted (a
28
+ * pathologically long approval id) — the caller then falls back to the text
29
+ * prompt rather than ship a half-rendered prompt.
30
+ *
31
+ * `allowAlways: false` drops the "Allow always" button (approvals where
32
+ * persisting an allowlist entry doesn't apply).
33
+ */
34
+ export function buildDiscordApprovalMessage(args) {
35
+ const specs = buildApprovalCallbackButtons({
36
+ approvalId: args.approvalId,
37
+ ...(args.allowAlways === false ? { allowAlways: false } : {}),
38
+ });
39
+ if (specs.length < 2)
40
+ return null; // not enough buttons → caller uses text prompt
41
+ const rows = buildDiscordApprovalRows(specs);
42
+ if (rows.length === 0)
43
+ return null;
44
+ const text = buildDiscordApprovalText({
45
+ command: args.command,
46
+ approvalKind: args.approvalKind,
47
+ ...(args.toolName !== undefined ? { toolName: args.toolName } : {}),
48
+ });
49
+ return { text, rows };
50
+ }
51
+ /**
52
+ * Compose the operator-facing approval question text rendered ABOVE the buttons.
53
+ * Kept short + control-char-scrubbed; the buttons carry the action, so the text
54
+ * only needs the command preview + a one-line ask. The 🦁 mark is the Brigade
55
+ * brand-stamp so the operator recognises this as a Brigade prompt. The command
56
+ * preview is wrapped in a Discord inline code span (single backticks) so it
57
+ * renders verbatim.
58
+ */
59
+ export function buildDiscordApprovalText(args) {
60
+ const flat = args.command
61
+ .replace(/[\r\n]+/g, " ")
62
+ // oxlint-disable-next-line no-control-regex
63
+ .replace(CONTROL_CHARS_RE, " ")
64
+ .replace(/\s+/g, " ")
65
+ .trim();
66
+ const preview = flat.length <= 180 ? flat : `${flat.slice(0, 177)}…`;
67
+ const what = args.approvalKind === "plugin" ? "run a plugin action" : "run a shell command";
68
+ const lines = [`🦁 Brigade wants to ${what}:`, `\`${preview}\``, "", "Choose below — times out in 5 minutes."];
69
+ return lines.join("\n");
70
+ }
71
+ /**
72
+ * Parse a pressed button's `custom_id` into the pending-approval id + decision,
73
+ * via the CENTRAL codec. Returns `null` when the press wasn't an approval button
74
+ * (a general button, or a foreign id) so the caller can fall through.
75
+ */
76
+ export function parseDiscordApprovalAction(customId) {
77
+ if (!customId)
78
+ return null;
79
+ return decodeApprovalCallback(customId);
80
+ }
81
+ //# sourceMappingURL=approval-native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-native.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/approval-native.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,4BAA4B,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACjF,OAAO,EAAE,wBAAwB,EAAyB,MAAM,iBAAiB,CAAC;AAElF,sFAAsF;AACtF,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAUjD;;;;;;;;GAQG;AACH,MAAM,UAAU,2BAA2B,CAAC,IAM3C;IACA,MAAM,KAAK,GAAG,4BAA4B,CAAC;QAC1C,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,GAAG,CAAC,IAAI,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7D,CAAC,CAAC;IACH,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,+CAA+C;IAClF,MAAM,IAAI,GAAG,wBAAwB,CAAC,KAAK,CAAC,CAAC;IAC7C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,IAAI,GAAG,wBAAwB,CAAC;QACrC,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,GAAG,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnE,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACvB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAKxC;IACA,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO;SACvB,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC;QACzB,4CAA4C;SAC3C,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC;SAC9B,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CAAC;IACT,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC;IACrE,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,KAAK,QAAQ,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,qBAAqB,CAAC;IAC5F,MAAM,KAAK,GAAG,CAAC,uBAAuB,IAAI,GAAG,EAAE,KAAK,OAAO,IAAI,EAAE,EAAE,EAAE,wCAAwC,CAAC,CAAC;IAC/G,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACzC,QAA4B;IAE5B,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,OAAO,sBAAsB,CAAC,QAAQ,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Discord native command menu — map Brigade's central channel commands onto the
3
+ * application-command payload that Discord's REST `PUT /applications/{id}/commands`
4
+ * (or the per-guild variant) registers, so the operator sees `/help`, `/status`,
5
+ * … as native Discord slash commands.
6
+ *
7
+ * Brigade owns the command set centrally (`buildBundledCommands` → `/help`,
8
+ * `/status`, `/allowlist`, `/agent`, `/agents`, `/whoami`, `/org`, plus any
9
+ * module-registered channel commands). Discord — like Telegram's `setMyCommands`
10
+ * but UNLIKE Slack (whose slash commands are configured by hand in the app UI) —
11
+ * accepts a programmatic registration, so on connect the connection PUTs this
12
+ * manifest. (Discord slash commands carry no positional args by default, so the
13
+ * agent receives `/status` etc. as a bare command; an inbound interaction is
14
+ * normalized back into a `/command` text turn.)
15
+ *
16
+ * Discord's constraints (enforced here so a malformed command never makes the
17
+ * whole `PUT` reject):
18
+ * - command name: 1–32 chars, lowercase, `[a-z0-9_-]` only (leading `/` stripped).
19
+ * - description: 1–100 chars (clamped; a non-empty fallback is always emitted
20
+ * since Discord rejects an empty description).
21
+ * - at most 100 global commands.
22
+ * - CHAT_INPUT command type = 1.
23
+ *
24
+ * Pure / deterministic — no I/O. Output command names are printable ASCII
25
+ * (`[a-z0-9_-]`), so no NUL / control byte can appear.
26
+ */
27
+ import type { ChannelCommand } from "../sdk.js";
28
+ /** A Discord application-command (CHAT_INPUT) registration entry. */
29
+ export interface DiscordApplicationCommand {
30
+ /** Command WITHOUT the leading slash, e.g. `status`. */
31
+ name: string;
32
+ /** Short description shown in the Discord command hint (1–100 chars). */
33
+ description: string;
34
+ /** CHAT_INPUT command type (1). */
35
+ type: 1;
36
+ }
37
+ /**
38
+ * Normalize a command word to Discord's `[a-z0-9_-]{1,32}` shape, or null if
39
+ * unusable. Strips a leading `/`, lowercases, and drops disallowed chars.
40
+ */
41
+ export declare function normalizeDiscordCommandName(raw: string): string | null;
42
+ /**
43
+ * Build the Discord application-command manifest from Brigade's central channel
44
+ * commands. De-dupes by normalized name (first wins), drops unusable names, and
45
+ * caps at Discord's 100-command ceiling. Returns `[]` when nothing maps (the
46
+ * connection then skips the REST `PUT` entirely).
47
+ */
48
+ export declare function buildDiscordCommandManifest(commands: ReadonlyArray<ChannelCommand>): DiscordApplicationCommand[];
49
+ //# sourceMappingURL=command-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-menu.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/command-menu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,qEAAqE;AACrE,MAAM,WAAW,yBAAyB;IACzC,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB,mCAAmC;IACnC,IAAI,EAAE,CAAC,CAAC;CACR;AAWD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKtE;AAUD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,aAAa,CAAC,cAAc,CAAC,GAAG,yBAAyB,EAAE,CAWhH"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Discord native command menu — map Brigade's central channel commands onto the
3
+ * application-command payload that Discord's REST `PUT /applications/{id}/commands`
4
+ * (or the per-guild variant) registers, so the operator sees `/help`, `/status`,
5
+ * … as native Discord slash commands.
6
+ *
7
+ * Brigade owns the command set centrally (`buildBundledCommands` → `/help`,
8
+ * `/status`, `/allowlist`, `/agent`, `/agents`, `/whoami`, `/org`, plus any
9
+ * module-registered channel commands). Discord — like Telegram's `setMyCommands`
10
+ * but UNLIKE Slack (whose slash commands are configured by hand in the app UI) —
11
+ * accepts a programmatic registration, so on connect the connection PUTs this
12
+ * manifest. (Discord slash commands carry no positional args by default, so the
13
+ * agent receives `/status` etc. as a bare command; an inbound interaction is
14
+ * normalized back into a `/command` text turn.)
15
+ *
16
+ * Discord's constraints (enforced here so a malformed command never makes the
17
+ * whole `PUT` reject):
18
+ * - command name: 1–32 chars, lowercase, `[a-z0-9_-]` only (leading `/` stripped).
19
+ * - description: 1–100 chars (clamped; a non-empty fallback is always emitted
20
+ * since Discord rejects an empty description).
21
+ * - at most 100 global commands.
22
+ * - CHAT_INPUT command type = 1.
23
+ *
24
+ * Pure / deterministic — no I/O. Output command names are printable ASCII
25
+ * (`[a-z0-9_-]`), so no NUL / control byte can appear.
26
+ */
27
+ /** Discord application-command limits. */
28
+ const MAX_COMMANDS = 100;
29
+ const MAX_NAME_LEN = 32;
30
+ const MAX_DESC_LEN = 100;
31
+ /** Discord allows lowercase `[a-z0-9_-]` (1–32) in a CHAT_INPUT command name. */
32
+ const COMMAND_NAME_RE = /^[a-z0-9_-]{1,32}$/;
33
+ /** CHAT_INPUT application-command type. */
34
+ const CHAT_INPUT = 1;
35
+ /**
36
+ * Normalize a command word to Discord's `[a-z0-9_-]{1,32}` shape, or null if
37
+ * unusable. Strips a leading `/`, lowercases, and drops disallowed chars.
38
+ */
39
+ export function normalizeDiscordCommandName(raw) {
40
+ const stripped = raw.trim().replace(/^\/+/, "").toLowerCase();
41
+ const cleaned = stripped.replace(/[^a-z0-9_-]/g, "").slice(0, MAX_NAME_LEN);
42
+ if (!cleaned || !COMMAND_NAME_RE.test(cleaned))
43
+ return null;
44
+ return cleaned;
45
+ }
46
+ /** Clamp + flatten a description to a single printable line within Discord's cap (never empty). */
47
+ function normalizeDescription(desc, fallback) {
48
+ const raw = (desc ?? "").replace(/\s+/g, " ").trim() || fallback;
49
+ const clamped = raw.length > MAX_DESC_LEN ? `${raw.slice(0, MAX_DESC_LEN - 1)}…` : raw;
50
+ // Discord rejects an empty description — guarantee at least the fallback name.
51
+ return clamped || fallback;
52
+ }
53
+ /**
54
+ * Build the Discord application-command manifest from Brigade's central channel
55
+ * commands. De-dupes by normalized name (first wins), drops unusable names, and
56
+ * caps at Discord's 100-command ceiling. Returns `[]` when nothing maps (the
57
+ * connection then skips the REST `PUT` entirely).
58
+ */
59
+ export function buildDiscordCommandManifest(commands) {
60
+ const out = [];
61
+ const seen = new Set();
62
+ for (const cmd of commands) {
63
+ if (out.length >= MAX_COMMANDS)
64
+ break;
65
+ const name = normalizeDiscordCommandName(cmd.name);
66
+ if (!name || seen.has(name))
67
+ continue;
68
+ seen.add(name);
69
+ out.push({ name, description: normalizeDescription(cmd.description, name), type: CHAT_INPUT });
70
+ }
71
+ return out;
72
+ }
73
+ //# sourceMappingURL=command-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-menu.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/command-menu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAcH,0CAA0C;AAC1C,MAAM,YAAY,GAAG,GAAG,CAAC;AACzB,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,YAAY,GAAG,GAAG,CAAC;AACzB,iFAAiF;AACjF,MAAM,eAAe,GAAG,oBAAoB,CAAC;AAC7C,2CAA2C;AAC3C,MAAM,UAAU,GAAG,CAAU,CAAC;AAE9B;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,GAAW;IACtD,MAAM,QAAQ,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9D,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;IAC5E,IAAI,CAAC,OAAO,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5D,OAAO,OAAO,CAAC;AAChB,CAAC;AAED,mGAAmG;AACnG,SAAS,oBAAoB,CAAC,IAAwB,EAAE,QAAgB;IACvE,MAAM,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,IAAI,QAAQ,CAAC;IACjE,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACvF,+EAA+E;IAC/E,OAAO,OAAO,IAAI,QAAQ,CAAC;AAC5B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,2BAA2B,CAAC,QAAuC;IAClF,MAAM,GAAG,GAAgC,EAAE,CAAC;IAC5C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,GAAG,CAAC,MAAM,IAAI,YAAY;YAAE,MAAM;QACtC,MAAM,IAAI,GAAG,2BAA2B,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QACtC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACf,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,oBAAoB,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC;IAChG,CAAC;IACD,OAAO,GAAG,CAAC;AACZ,CAAC"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Discord message-component button rendering + the custom_id callback codec.
3
+ *
4
+ * Discord's analogue of `slack/blocks.ts`. Two button lanes share one builder,
5
+ * exactly as Slack's do:
6
+ *
7
+ * - APPROVAL buttons — payloads come from the CENTRAL approval codec
8
+ * (`buildApprovalCallbackButtons`), so a press resolves through the central
9
+ * `tryConsumeChannelApprovalCallback`. Built in `approval-native.ts`; this
10
+ * module only supplies the low-level row + button shapes.
11
+ * - GENERAL buttons — the agent attaches arbitrary buttons via the
12
+ * `message_action` `buttons` kind; a press routes back through the inbound
13
+ * pipeline as a normal turn. Every general payload is namespaced with
14
+ * {@link GENERAL_CALLBACK_PREFIX} so the pipeline can tell it apart from an
15
+ * approval press (which it consumes FIRST).
16
+ *
17
+ * WHERE THE PAYLOAD RIDES. A Discord button carries its opaque codec payload in
18
+ * `custom_id` (Discord caps it at 100 chars). The central approval codec already
19
+ * guarantees ≤64 UTF-8 bytes, so an approval payload always fits; a general
20
+ * payload that exceeds the budget after prefixing is DROPPED (rather than ship a
21
+ * truncated id that decodes to the wrong action on press). On press, discord.js
22
+ * delivers the pressed `interaction.customId` verbatim, which the central
23
+ * pipeline decodes — there is no separate routing id (unlike Slack's action_id).
24
+ *
25
+ * These builders emit PLAIN serializable specs (a `DiscordButtonSpec` grid); the
26
+ * connection turns each into a discord.js `ActionRowBuilder<ButtonBuilder>` at
27
+ * send time, so this module stays pure + dependency-light + unit-testable
28
+ * without importing discord.js.
29
+ *
30
+ * Pure / deterministic — no I/O, no globals.
31
+ */
32
+ /** Discord caps a component `custom_id` at 100 chars. */
33
+ export declare const DISCORD_CUSTOM_ID_MAX_CHARS = 100;
34
+ /** Discord allows at most 5 buttons per action row. */
35
+ export declare const DISCORD_BUTTONS_PER_ROW = 5;
36
+ /** Discord allows at most 5 action rows per message. */
37
+ export declare const DISCORD_MAX_ROWS = 5;
38
+ /** Discord ButtonStyle enum values (mirrors discord.js `ButtonStyle`). */
39
+ export declare const DISCORD_BUTTON_STYLE: {
40
+ readonly Primary: 1;
41
+ readonly Secondary: 2;
42
+ readonly Success: 3;
43
+ readonly Danger: 4;
44
+ };
45
+ export type DiscordButtonStyleValue = (typeof DISCORD_BUTTON_STYLE)[keyof typeof DISCORD_BUTTON_STYLE];
46
+ /** A single serializable Discord button spec (the connection turns it into a ButtonBuilder). */
47
+ export interface DiscordButtonSpec {
48
+ /** Button label shown to the user (≤ 80 chars). */
49
+ label: string;
50
+ /** Opaque codec payload carried in `custom_id` (≤ 100 chars), read back on press. */
51
+ customId: string;
52
+ /** Discord button style (default Secondary). */
53
+ style: DiscordButtonStyleValue;
54
+ }
55
+ /** One action row — up to 5 buttons. */
56
+ export type DiscordActionRow = DiscordButtonSpec[];
57
+ /** One button spec before it's shaped + validated. */
58
+ export interface DiscordButtonInput {
59
+ /** Button label shown to the user. */
60
+ text: string;
61
+ /** Opaque codec payload delivered to the handler on press. */
62
+ value: string;
63
+ /** Optional Discord button style. */
64
+ style?: DiscordButtonStyleValue;
65
+ }
66
+ /**
67
+ * Strip control bytes from a custom_id. The central codec already guarantees a
68
+ * printable, short payload, so this is purely defensive for a value that didn't
69
+ * come from the codec — and we never truncate a payload (we DROP an over-budget
70
+ * one at the call site), since a truncated id would decode to the wrong action.
71
+ */
72
+ export declare function sanitizeDiscordCustomId(value: string): string;
73
+ /**
74
+ * Build the action rows for an APPROVAL prompt from the central codec's
75
+ * `{ label, data, decision }` specs (the payload rides in `custom_id`). Returns
76
+ * `[]` when fewer than two byte-safe buttons could be shaped — the caller then
77
+ * falls back to a text prompt rather than ship a half-rendered prompt. `Deny` is
78
+ * styled Danger; the first (allow-once) button Success.
79
+ */
80
+ export declare function buildDiscordApprovalRows(specs: ReadonlyArray<{
81
+ label: string;
82
+ data: string;
83
+ decision?: string;
84
+ }>): DiscordActionRow[];
85
+ /**
86
+ * Build the action rows for a GENERAL button keyboard from a grid of specs. Each
87
+ * spec's `data` is prefixed with {@link GENERAL_CALLBACK_PREFIX} + sanitized; a
88
+ * button whose prefixed id exceeds the budget OR whose label is empty is
89
+ * DROPPED. Returns `null` when no usable button remains (the caller then sends a
90
+ * plain message instead). The grid is flattened + re-chunked into Discord's
91
+ * 5-per-row / 5-row limits.
92
+ */
93
+ export declare function buildDiscordButtonRows(grid: Array<Array<{
94
+ text: string;
95
+ data: string;
96
+ }>>): DiscordActionRow[] | null;
97
+ //# sourceMappingURL=components.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/discord/components.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAIH,yDAAyD;AACzD,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAE/C,uDAAuD;AACvD,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,wDAAwD;AACxD,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAKlC,0EAA0E;AAC1E,eAAO,MAAM,oBAAoB;;;;;CAKvB,CAAC;AAEX,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,OAAO,oBAAoB,CAAC,CAAC;AAEvG,gGAAgG;AAChG,MAAM,WAAW,iBAAiB;IACjC,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,KAAK,EAAE,uBAAuB,CAAC;CAC/B;AAED,wCAAwC;AACxC,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,EAAE,CAAC;AAEnD,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IAClC,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,qCAAqC;IACrC,KAAK,CAAC,EAAE,uBAAuB,CAAC;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAG7D;AA0BD;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACvC,KAAK,EAAE,aAAa,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GACtE,gBAAgB,EAAE,CAcpB;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,GAAG,gBAAgB,EAAE,GAAG,IAAI,CAcpH"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Discord message-component button rendering + the custom_id callback codec.
3
+ *
4
+ * Discord's analogue of `slack/blocks.ts`. Two button lanes share one builder,
5
+ * exactly as Slack's do:
6
+ *
7
+ * - APPROVAL buttons — payloads come from the CENTRAL approval codec
8
+ * (`buildApprovalCallbackButtons`), so a press resolves through the central
9
+ * `tryConsumeChannelApprovalCallback`. Built in `approval-native.ts`; this
10
+ * module only supplies the low-level row + button shapes.
11
+ * - GENERAL buttons — the agent attaches arbitrary buttons via the
12
+ * `message_action` `buttons` kind; a press routes back through the inbound
13
+ * pipeline as a normal turn. Every general payload is namespaced with
14
+ * {@link GENERAL_CALLBACK_PREFIX} so the pipeline can tell it apart from an
15
+ * approval press (which it consumes FIRST).
16
+ *
17
+ * WHERE THE PAYLOAD RIDES. A Discord button carries its opaque codec payload in
18
+ * `custom_id` (Discord caps it at 100 chars). The central approval codec already
19
+ * guarantees ≤64 UTF-8 bytes, so an approval payload always fits; a general
20
+ * payload that exceeds the budget after prefixing is DROPPED (rather than ship a
21
+ * truncated id that decodes to the wrong action on press). On press, discord.js
22
+ * delivers the pressed `interaction.customId` verbatim, which the central
23
+ * pipeline decodes — there is no separate routing id (unlike Slack's action_id).
24
+ *
25
+ * These builders emit PLAIN serializable specs (a `DiscordButtonSpec` grid); the
26
+ * connection turns each into a discord.js `ActionRowBuilder<ButtonBuilder>` at
27
+ * send time, so this module stays pure + dependency-light + unit-testable
28
+ * without importing discord.js.
29
+ *
30
+ * Pure / deterministic — no I/O, no globals.
31
+ */
32
+ import { GENERAL_CALLBACK_PREFIX } from "../general-callback.js";
33
+ /** Discord caps a component `custom_id` at 100 chars. */
34
+ export const DISCORD_CUSTOM_ID_MAX_CHARS = 100;
35
+ /** Discord allows at most 5 buttons per action row. */
36
+ export const DISCORD_BUTTONS_PER_ROW = 5;
37
+ /** Discord allows at most 5 action rows per message. */
38
+ export const DISCORD_MAX_ROWS = 5;
39
+ /** Discord button label cap (chars). */
40
+ const DISCORD_BUTTON_LABEL_MAX = 80;
41
+ /** Discord ButtonStyle enum values (mirrors discord.js `ButtonStyle`). */
42
+ export const DISCORD_BUTTON_STYLE = {
43
+ Primary: 1,
44
+ Secondary: 2,
45
+ Success: 3,
46
+ Danger: 4,
47
+ };
48
+ /**
49
+ * Strip control bytes from a custom_id. The central codec already guarantees a
50
+ * printable, short payload, so this is purely defensive for a value that didn't
51
+ * come from the codec — and we never truncate a payload (we DROP an over-budget
52
+ * one at the call site), since a truncated id would decode to the wrong action.
53
+ */
54
+ export function sanitizeDiscordCustomId(value) {
55
+ // oxlint-disable-next-line no-control-regex
56
+ return value.replace(/[\x00-\x1f\x7f-\x9f]/g, "");
57
+ }
58
+ /** Shape one button input into a validated spec, or null when it can't fit. */
59
+ function toButton(input) {
60
+ const label = (input?.text ?? "").trim();
61
+ const customId = sanitizeDiscordCustomId(input?.value ?? "");
62
+ if (!label || !customId)
63
+ return null;
64
+ // Reject (don't truncate) an id that won't fit — a truncated payload would
65
+ // decode to the wrong action on press.
66
+ if (customId.length > DISCORD_CUSTOM_ID_MAX_CHARS)
67
+ return null;
68
+ return {
69
+ label: label.slice(0, DISCORD_BUTTON_LABEL_MAX),
70
+ customId,
71
+ style: input.style ?? DISCORD_BUTTON_STYLE.Secondary,
72
+ };
73
+ }
74
+ /** Group a flat button list into rows of at most 5, capped at 5 rows total. */
75
+ function chunkIntoRows(buttons) {
76
+ const rows = [];
77
+ for (let i = 0; i < buttons.length && rows.length < DISCORD_MAX_ROWS; i += DISCORD_BUTTONS_PER_ROW) {
78
+ rows.push(buttons.slice(i, i + DISCORD_BUTTONS_PER_ROW));
79
+ }
80
+ return rows;
81
+ }
82
+ /**
83
+ * Build the action rows for an APPROVAL prompt from the central codec's
84
+ * `{ label, data, decision }` specs (the payload rides in `custom_id`). Returns
85
+ * `[]` when fewer than two byte-safe buttons could be shaped — the caller then
86
+ * falls back to a text prompt rather than ship a half-rendered prompt. `Deny` is
87
+ * styled Danger; the first (allow-once) button Success.
88
+ */
89
+ export function buildDiscordApprovalRows(specs) {
90
+ const buttons = [];
91
+ for (const s of specs) {
92
+ const style = s.decision === "deny"
93
+ ? DISCORD_BUTTON_STYLE.Danger
94
+ : s.decision === "allow-once"
95
+ ? DISCORD_BUTTON_STYLE.Success
96
+ : DISCORD_BUTTON_STYLE.Secondary;
97
+ const btn = toButton({ text: s.label, value: s.data, style });
98
+ if (btn)
99
+ buttons.push(btn);
100
+ }
101
+ if (buttons.length < 2)
102
+ return []; // not enough buttons → caller uses text prompt
103
+ return chunkIntoRows(buttons);
104
+ }
105
+ /**
106
+ * Build the action rows for a GENERAL button keyboard from a grid of specs. Each
107
+ * spec's `data` is prefixed with {@link GENERAL_CALLBACK_PREFIX} + sanitized; a
108
+ * button whose prefixed id exceeds the budget OR whose label is empty is
109
+ * DROPPED. Returns `null` when no usable button remains (the caller then sends a
110
+ * plain message instead). The grid is flattened + re-chunked into Discord's
111
+ * 5-per-row / 5-row limits.
112
+ */
113
+ export function buildDiscordButtonRows(grid) {
114
+ const buttons = [];
115
+ for (const row of grid) {
116
+ for (const spec of row) {
117
+ const label = (spec?.text ?? "").trim();
118
+ const token = spec?.data ?? "";
119
+ if (!label || !token)
120
+ continue;
121
+ const prefixed = `${GENERAL_CALLBACK_PREFIX}${token}`;
122
+ const btn = toButton({ text: label, value: prefixed });
123
+ if (btn)
124
+ buttons.push(btn);
125
+ }
126
+ }
127
+ if (buttons.length === 0)
128
+ return null;
129
+ return chunkIntoRows(buttons);
130
+ }
131
+ //# sourceMappingURL=components.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.js","sourceRoot":"","sources":["../../../../src/agents/channels/discord/components.ts"],"names":[],"mappings":"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"}