@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,43 @@
1
+ /**
2
+ * Slack inline-approval authorization.
3
+ *
4
+ * When an approval prompt is rendered as Block Kit buttons, ANY member who can
5
+ * see the message could press a button. Brigade's central inbound pipeline
6
+ * already runs the access-control gate before a `block_actions` press reaches
7
+ * the approval-callback path, so only an admitted (allow-listed / owner) peer
8
+ * gets here at all — but a SHARED Slack channel is the edge case: in a channel
9
+ * the bot is in, an admitted member's button press should still be allowed only
10
+ * when that presser is an approved approver, not merely present in the room.
11
+ * Slack's multi-member workspaces make this gate more load-bearing than
12
+ * Telegram's.
13
+ *
14
+ * This predicate is the channel's `approvalCapability.authorizeApprover`. It is
15
+ * invoked CENTRALLY by `tryConsumeChannelApprovalCallback` with the presser's
16
+ * `senderId` (the Slack user id `U…`); returning `{ authorized: false, reason }`
17
+ * refuses the press without consuming the operator's pending approval (so the
18
+ * real operator can still answer). Policy:
19
+ *
20
+ * - When the channel has an explicit allow-from list configured (the approved
21
+ * senders), only those ids may approve.
22
+ * - When NO allow-from list is configured, defer to the access gate that
23
+ * already admitted the inbound and authorize the press (matches the text-
24
+ * reply path, which has no extra approver gate).
25
+ *
26
+ * Pure + deterministic over its `cfg` + `senderId` inputs — no I/O.
27
+ */
28
+ import type { BrigadeConfig } from "../../../config/io.js";
29
+ /**
30
+ * Resolve whether `senderId` is allowed to answer a Slack inline approval.
31
+ * Returns `{ authorized: true }` when no explicit allow-from gate applies (the
32
+ * central access gate already admitted the inbound), or when the presser is on
33
+ * the configured allow-from list. Otherwise refuses with a reason.
34
+ */
35
+ export declare function resolveSlackApprover(args: {
36
+ cfg: BrigadeConfig;
37
+ senderId?: string;
38
+ accountId?: string;
39
+ }): {
40
+ authorized: boolean;
41
+ reason?: string;
42
+ };
43
+ //# sourceMappingURL=approval-authorize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-authorize.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/approval-authorize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AA0B3D;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IAC1C,GAAG,EAAE,aAAa,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB,GAAG;IAAE,UAAU,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAU3C"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Slack inline-approval authorization.
3
+ *
4
+ * When an approval prompt is rendered as Block Kit buttons, ANY member who can
5
+ * see the message could press a button. Brigade's central inbound pipeline
6
+ * already runs the access-control gate before a `block_actions` press reaches
7
+ * the approval-callback path, so only an admitted (allow-listed / owner) peer
8
+ * gets here at all — but a SHARED Slack channel is the edge case: in a channel
9
+ * the bot is in, an admitted member's button press should still be allowed only
10
+ * when that presser is an approved approver, not merely present in the room.
11
+ * Slack's multi-member workspaces make this gate more load-bearing than
12
+ * Telegram's.
13
+ *
14
+ * This predicate is the channel's `approvalCapability.authorizeApprover`. It is
15
+ * invoked CENTRALLY by `tryConsumeChannelApprovalCallback` with the presser's
16
+ * `senderId` (the Slack user id `U…`); returning `{ authorized: false, reason }`
17
+ * refuses the press without consuming the operator's pending approval (so the
18
+ * real operator can still answer). Policy:
19
+ *
20
+ * - When the channel has an explicit allow-from list configured (the approved
21
+ * senders), only those ids may approve.
22
+ * - When NO allow-from list is configured, defer to the access gate that
23
+ * already admitted the inbound and authorize the press (matches the text-
24
+ * reply path, which has no extra approver gate).
25
+ *
26
+ * Pure + deterministic over its `cfg` + `senderId` inputs — no I/O.
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?.slack;
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-workspace 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 Slack 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 resolveSlackApprover(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/slack/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,KAEX,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,gFAAgF;IAChF,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,oBAAoB,CAAC,IAIpC;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,70 @@
1
+ /**
2
+ * Slack Block Kit rendering for native approval prompts.
3
+ *
4
+ * When a channel-routed turn raises an exec/plugin approval AND the Slack
5
+ * adapter has opted into `approvalCapability.sendApprovalPrompt`, the central
6
+ * approval-router asks this channel to render the question as native Block Kit
7
+ * BUTTONS instead of the default "reply yes/no" text card. The button payloads
8
+ * are produced by the CENTRAL codec (`buildApprovalCallbackButtons`) so the
9
+ * press comes back as an `InboundMessage.callbackQuery` the central
10
+ * `tryConsumeChannelApprovalCallback` decodes + resolves — this file only maps
11
+ * the codec's `{ label, data }` specs onto Slack's `actions`-block shape (via
12
+ * `blocks.ts`) and assembles the prompt text + fallback.
13
+ *
14
+ * SAFETY: every button `value` here is the codec's output — versioned,
15
+ * base64url + printable-ASCII, already proven `<= 64` UTF-8 bytes by
16
+ * `encodeApprovalCallback` (and well under Slack's 255-char `value` cap). This
17
+ * module never mints its own payloads.
18
+ *
19
+ * Slack mirror of `telegram/approval-native.ts`.
20
+ */
21
+ import { type SlackBlock, type SlackBlockAction } from "./blocks.js";
22
+ /** The Slack message payload an approval prompt sends: a fallback `text` + `blocks`. */
23
+ export interface SlackApprovalMessage {
24
+ /** Plain fallback text (notifications + clients that can't render blocks). */
25
+ text: string;
26
+ /** The Block Kit blocks: a section (prompt) + the actions block(s). */
27
+ blocks: SlackBlock[];
28
+ }
29
+ /**
30
+ * Build the Block Kit 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 buildSlackApprovalMessage(args: {
39
+ approvalId: string;
40
+ command: string;
41
+ approvalKind: "exec" | "plugin";
42
+ toolName?: string;
43
+ allowAlways?: boolean;
44
+ }): SlackApprovalMessage | 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 Slack code span (single backticks) so it renders
51
+ * verbatim.
52
+ */
53
+ export declare function buildSlackApprovalText(args: {
54
+ command: string;
55
+ approvalKind: "exec" | "plugin";
56
+ toolName?: string;
57
+ agentId?: string;
58
+ }): string;
59
+ /**
60
+ * Parse a `block_actions` interaction's button press into the pending-approval
61
+ * id + decision. Pulls the codec payload out of the first Brigade-owned approval
62
+ * action (via {@link extractBlockActionPayload}) and decodes it with the CENTRAL
63
+ * codec. Returns `null` when the press wasn't an approval button (a general
64
+ * button, or someone else's action) so the caller can fall through.
65
+ */
66
+ export declare function parseSlackApprovalAction(actions: ReadonlyArray<SlackBlockAction> | undefined): {
67
+ approvalId: string;
68
+ decision: "allow-once" | "allow-always" | "deny";
69
+ } | null;
70
+ //# sourceMappingURL=approval-native.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-native.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/approval-native.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,EAIN,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,MAAM,aAAa,CAAC;AAKrB,wFAAwF;AACxF,MAAM,WAAW,oBAAoB;IACpC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,MAAM,EAAE,UAAU,EAAE,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC/C,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,oBAAoB,GAAG,IAAI,CAe9B;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC5C,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;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACvC,OAAO,EAAE,aAAa,CAAC,gBAAgB,CAAC,GAAG,SAAS,GAClD;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,YAAY,GAAG,cAAc,GAAG,MAAM,CAAA;CAAE,GAAG,IAAI,CAIjF"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Slack Block Kit rendering for native approval prompts.
3
+ *
4
+ * When a channel-routed turn raises an exec/plugin approval AND the Slack
5
+ * adapter has opted into `approvalCapability.sendApprovalPrompt`, the central
6
+ * approval-router asks this channel to render the question as native Block Kit
7
+ * BUTTONS instead of the default "reply yes/no" text card. The button payloads
8
+ * are produced by the CENTRAL codec (`buildApprovalCallbackButtons`) so the
9
+ * press comes back as an `InboundMessage.callbackQuery` the central
10
+ * `tryConsumeChannelApprovalCallback` decodes + resolves — this file only maps
11
+ * the codec's `{ label, data }` specs onto Slack's `actions`-block shape (via
12
+ * `blocks.ts`) and assembles the prompt text + fallback.
13
+ *
14
+ * SAFETY: every button `value` here is the codec's output — versioned,
15
+ * base64url + printable-ASCII, already proven `<= 64` UTF-8 bytes by
16
+ * `encodeApprovalCallback` (and well under Slack's 255-char `value` cap). This
17
+ * module never mints its own payloads.
18
+ *
19
+ * Slack mirror of `telegram/approval-native.ts`.
20
+ */
21
+ import { buildApprovalCallbackButtons, decodeApprovalCallback } from "../sdk.js";
22
+ import { buildSlackApprovalBlocks, extractBlockActionPayload, } from "./blocks.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 Block Kit 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 buildSlackApprovalMessage(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 actionBlocks = buildSlackApprovalBlocks(specs);
42
+ if (actionBlocks.length === 0)
43
+ return null;
44
+ const promptText = buildSlackApprovalText({
45
+ command: args.command,
46
+ approvalKind: args.approvalKind,
47
+ ...(args.toolName !== undefined ? { toolName: args.toolName } : {}),
48
+ });
49
+ const blocks = [{ type: "section", text: { type: "mrkdwn", text: promptText } }, ...actionBlocks];
50
+ return { text: promptText, blocks };
51
+ }
52
+ /**
53
+ * Compose the operator-facing approval question text rendered ABOVE the buttons.
54
+ * Kept short + control-char-scrubbed; the buttons carry the action, so the text
55
+ * only needs the command preview + a one-line ask. The 🦁 mark is the Brigade
56
+ * brand-stamp so the operator recognises this as a Brigade prompt. The command
57
+ * preview is wrapped in a Slack code span (single backticks) so it renders
58
+ * verbatim.
59
+ */
60
+ export function buildSlackApprovalText(args) {
61
+ const flat = args.command
62
+ .replace(/[\r\n]+/g, " ")
63
+ // oxlint-disable-next-line no-control-regex
64
+ .replace(CONTROL_CHARS_RE, " ")
65
+ .replace(/\s+/g, " ")
66
+ .trim();
67
+ const preview = flat.length <= 180 ? flat : `${flat.slice(0, 177)}…`;
68
+ const what = args.approvalKind === "plugin" ? "run a plugin action" : "run a shell command";
69
+ const lines = [`🦁 Brigade wants to ${what}:`, `\`${preview}\``, "", "Choose below — times out in 5 minutes."];
70
+ return lines.join("\n");
71
+ }
72
+ /**
73
+ * Parse a `block_actions` interaction's button press into the pending-approval
74
+ * id + decision. Pulls the codec payload out of the first Brigade-owned approval
75
+ * action (via {@link extractBlockActionPayload}) and decodes it with the CENTRAL
76
+ * codec. Returns `null` when the press wasn't an approval button (a general
77
+ * button, or someone else's action) so the caller can fall through.
78
+ */
79
+ export function parseSlackApprovalAction(actions) {
80
+ const payload = extractBlockActionPayload(actions);
81
+ if (!payload)
82
+ return null;
83
+ return decodeApprovalCallback(payload);
84
+ }
85
+ //# sourceMappingURL=approval-native.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval-native.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/approval-native.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,4BAA4B,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACjF,OAAO,EACN,wBAAwB,EACxB,yBAAyB,GAIzB,MAAM,aAAa,CAAC;AAErB,sFAAsF;AACtF,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAUjD;;;;;;;;GAQG;AACH,MAAM,UAAU,yBAAyB,CAAC,IAMzC;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,YAAY,GAAwB,wBAAwB,CAAC,KAAK,CAAC,CAAC;IAC1E,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,UAAU,GAAG,sBAAsB,CAAC;QACzC,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,MAAM,MAAM,GAAiB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,GAAG,YAAY,CAAC,CAAC;IAChH,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAKtC;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;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACvC,OAAoD;IAEpD,MAAM,OAAO,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;IACnD,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,OAAO,sBAAsB,CAAC,OAAO,CAAC,CAAC;AACxC,CAAC"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Slack Block Kit button rendering + the action_id / value callback codec.
3
+ *
4
+ * Slack's analogue of `telegram/inline-keyboard.ts`. Two button lanes share one
5
+ * builder, exactly as Telegram'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 `actions`-block + 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 Slack button carries BOTH an `action_id` and a
18
+ * `value`. The opaque codec payload rides in `value` (Slack caps it at 255
19
+ * chars — far more generous than Telegram's 64-byte `callback_data`, and the
20
+ * central codec already guarantees ≤64 bytes). The `action_id` is a stable,
21
+ * non-colliding constant ({@link SLACK_APPROVAL_ACTION_ID} /
22
+ * {@link SLACK_GENERAL_ACTION_ID}) so the interactive handler can route the
23
+ * payload without parsing it. A button whose `value` exceeds the cap OR whose
24
+ * label is empty is DROPPED (rather than ship a truncated payload that decodes
25
+ * to the wrong action).
26
+ *
27
+ * Pure / deterministic — no I/O, no globals.
28
+ */
29
+ /** Slack caps a button's `value` at 2000 chars; we hold to a tight 255 budget. */
30
+ export declare const SLACK_ACTION_VALUE_MAX_CHARS = 255;
31
+ /** Slack allows at most 5 elements per `actions` block; group buttons in fives. */
32
+ export declare const SLACK_ACTIONS_BLOCK_MAX = 5;
33
+ /** Stable `action_id` an APPROVAL button carries (routes to the approval path). */
34
+ export declare const SLACK_APPROVAL_ACTION_ID = "brigade_approval";
35
+ /** Stable `action_id` a GENERAL (agent-attached) button carries. */
36
+ export declare const SLACK_GENERAL_ACTION_ID = "brigade_general";
37
+ /** A Slack Block Kit button element (the subset Brigade emits). */
38
+ export interface SlackButtonElement {
39
+ type: "button";
40
+ text: {
41
+ type: "plain_text";
42
+ text: string;
43
+ emoji: boolean;
44
+ };
45
+ /** Routing id — one of the stable `SLACK_*_ACTION_ID` constants. */
46
+ action_id: string;
47
+ /** Opaque codec payload (≤ 255 chars), read back on press. */
48
+ value: string;
49
+ /** Optional visual style (Slack: `primary` green / `danger` red). */
50
+ style?: "primary" | "danger";
51
+ }
52
+ /** A Slack `actions` block — a row of up to 5 interactive elements. */
53
+ export interface SlackActionsBlock {
54
+ type: "actions";
55
+ /** Optional stable id so the block is addressable in the interaction payload. */
56
+ block_id?: string;
57
+ elements: SlackButtonElement[];
58
+ }
59
+ /** A Slack `section` block carrying mrkdwn text (the prompt body above buttons). */
60
+ export interface SlackSectionBlock {
61
+ type: "section";
62
+ text: {
63
+ type: "mrkdwn";
64
+ text: string;
65
+ };
66
+ }
67
+ /** Any block this module emits (section + actions). */
68
+ export type SlackBlock = SlackSectionBlock | SlackActionsBlock;
69
+ /** One button spec before it's shaped into a Block Kit element. */
70
+ export interface SlackButtonSpec {
71
+ /** Button label shown to the user. */
72
+ text: string;
73
+ /** Opaque codec payload delivered to the handler on press. */
74
+ value: string;
75
+ /** Optional Slack button style. */
76
+ style?: "primary" | "danger";
77
+ }
78
+ /**
79
+ * Clamp a button value to Slack's budget + strip control bytes. The central
80
+ * codec already guarantees a printable, short payload, so this is purely
81
+ * defensive for a value that didn't come from the codec — and unlike Telegram we
82
+ * never truncate a codec payload (the budget is far larger than the codec's
83
+ * output), we only DROP an over-budget value at the call site.
84
+ */
85
+ export declare function sanitizeSlackActionValue(value: string): string;
86
+ /**
87
+ * Build the `actions` blocks for an APPROVAL prompt from the central codec's
88
+ * `{ label, data }` specs (the payload rides in `value`). Returns `[]` when
89
+ * fewer than two byte-safe buttons could be shaped — the caller then falls back
90
+ * to a text prompt rather than ship a half-rendered prompt. `Deny` is styled
91
+ * `danger`; the first button `primary`.
92
+ */
93
+ export declare function buildSlackApprovalBlocks(specs: ReadonlyArray<{
94
+ label: string;
95
+ data: string;
96
+ decision?: string;
97
+ }>): SlackActionsBlock[];
98
+ /**
99
+ * Build the `actions` blocks for a GENERAL inline keyboard from a grid of button
100
+ * specs. Each spec's `data` is prefixed with {@link GENERAL_CALLBACK_PREFIX} +
101
+ * sanitized; a button whose prefixed value exceeds the budget OR whose label is
102
+ * empty is DROPPED. Returns `null` when no usable button remains (the caller
103
+ * then sends a plain message instead). The grid is flattened + re-chunked into
104
+ * Slack's 5-per-block rows.
105
+ */
106
+ export declare function buildSlackInlineKeyboard(grid: Array<Array<{
107
+ text: string;
108
+ data: string;
109
+ }>>): SlackActionsBlock[] | null;
110
+ /** A parsed `block_actions` button press (the subset Brigade reads). */
111
+ export interface SlackBlockAction {
112
+ action_id?: string;
113
+ value?: string;
114
+ block_id?: string;
115
+ }
116
+ /**
117
+ * Pull the opaque codec payload out of a `block_actions` interaction's first
118
+ * Brigade-owned button action. Returns the `value` string the button declared
119
+ * (an approval-codec payload OR a general-prefixed token) so the adapter can
120
+ * stamp it onto `InboundMessage.callbackQuery.data` for the central pipeline to
121
+ * decode — exactly as Telegram surfaces `callback_query.data`. Returns null when
122
+ * no action carried one of our `action_id`s.
123
+ */
124
+ export declare function extractBlockActionPayload(actions: ReadonlyArray<SlackBlockAction> | undefined): string | null;
125
+ //# sourceMappingURL=blocks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocks.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/blocks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,kFAAkF;AAClF,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAEzC,mFAAmF;AACnF,eAAO,MAAM,wBAAwB,qBAAqB,CAAC;AAE3D,oEAAoE;AACpE,eAAO,MAAM,uBAAuB,oBAAoB,CAAC;AAEzD,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE;QAAE,IAAI,EAAE,YAAY,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAC3D,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC7B;AAED,uEAAuE;AACvE,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,SAAS,CAAC;IAChB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,kBAAkB,EAAE,CAAC;CAC/B;AAED,oFAAoF;AACpF,MAAM,WAAW,iBAAiB;IACjC,IAAI,EAAE,SAAS,CAAC;IAChB,IAAI,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC;AAED,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;AAE/D,mEAAmE;AACnE,MAAM,WAAW,eAAe;IAC/B,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,mCAAmC;IACnC,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAI9D;AAgCD;;;;;;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,iBAAiB,EAAE,CAUrB;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,GAAG,iBAAiB,EAAE,GAAG,IAAI,CAcvH;AAED,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,aAAa,CAAC,gBAAgB,CAAC,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAQ7G"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Slack Block Kit button rendering + the action_id / value callback codec.
3
+ *
4
+ * Slack's analogue of `telegram/inline-keyboard.ts`. Two button lanes share one
5
+ * builder, exactly as Telegram'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 `actions`-block + 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 Slack button carries BOTH an `action_id` and a
18
+ * `value`. The opaque codec payload rides in `value` (Slack caps it at 255
19
+ * chars — far more generous than Telegram's 64-byte `callback_data`, and the
20
+ * central codec already guarantees ≤64 bytes). The `action_id` is a stable,
21
+ * non-colliding constant ({@link SLACK_APPROVAL_ACTION_ID} /
22
+ * {@link SLACK_GENERAL_ACTION_ID}) so the interactive handler can route the
23
+ * payload without parsing it. A button whose `value` exceeds the cap OR whose
24
+ * label is empty is DROPPED (rather than ship a truncated payload that decodes
25
+ * to the wrong action).
26
+ *
27
+ * Pure / deterministic — no I/O, no globals.
28
+ */
29
+ import { GENERAL_CALLBACK_PREFIX } from "../general-callback.js";
30
+ /** Slack caps a button's `value` at 2000 chars; we hold to a tight 255 budget. */
31
+ export const SLACK_ACTION_VALUE_MAX_CHARS = 255;
32
+ /** Slack allows at most 5 elements per `actions` block; group buttons in fives. */
33
+ export const SLACK_ACTIONS_BLOCK_MAX = 5;
34
+ /** Stable `action_id` an APPROVAL button carries (routes to the approval path). */
35
+ export const SLACK_APPROVAL_ACTION_ID = "brigade_approval";
36
+ /** Stable `action_id` a GENERAL (agent-attached) button carries. */
37
+ export const SLACK_GENERAL_ACTION_ID = "brigade_general";
38
+ /**
39
+ * Clamp a button value to Slack's budget + strip control bytes. The central
40
+ * codec already guarantees a printable, short payload, so this is purely
41
+ * defensive for a value that didn't come from the codec — and unlike Telegram we
42
+ * never truncate a codec payload (the budget is far larger than the codec's
43
+ * output), we only DROP an over-budget value at the call site.
44
+ */
45
+ export function sanitizeSlackActionValue(value) {
46
+ // Drop C0/C1 control chars (incl. NUL) — a button value must be printable.
47
+ // oxlint-disable-next-line no-control-regex
48
+ return value.replace(/[\x00-\x1f\x7f-\x9f]/g, "");
49
+ }
50
+ /** Shape one button spec into a Block Kit element, or null when it can't fit. */
51
+ function toButtonElement(spec, actionId) {
52
+ const label = (spec?.text ?? "").trim();
53
+ const value = sanitizeSlackActionValue(spec?.value ?? "");
54
+ if (!label || !value)
55
+ return null;
56
+ // Reject (don't truncate) a value that won't fit — a truncated payload would
57
+ // decode to the wrong action on press.
58
+ if (value.length > SLACK_ACTION_VALUE_MAX_CHARS)
59
+ return null;
60
+ return {
61
+ type: "button",
62
+ text: { type: "plain_text", text: label.slice(0, 75), emoji: true },
63
+ action_id: actionId,
64
+ value,
65
+ ...(spec.style ? { style: spec.style } : {}),
66
+ };
67
+ }
68
+ /** Group a flat element list into `actions` blocks of at most 5 (Slack's cap). */
69
+ function chunkIntoActionBlocks(elements, blockIdPrefix) {
70
+ const blocks = [];
71
+ for (let i = 0; i < elements.length; i += SLACK_ACTIONS_BLOCK_MAX) {
72
+ blocks.push({
73
+ type: "actions",
74
+ block_id: `${blockIdPrefix}_${i / SLACK_ACTIONS_BLOCK_MAX}`,
75
+ elements: elements.slice(i, i + SLACK_ACTIONS_BLOCK_MAX),
76
+ });
77
+ }
78
+ return blocks;
79
+ }
80
+ /**
81
+ * Build the `actions` blocks for an APPROVAL prompt from the central codec's
82
+ * `{ label, data }` specs (the payload rides in `value`). Returns `[]` when
83
+ * fewer than two byte-safe buttons could be shaped — the caller then falls back
84
+ * to a text prompt rather than ship a half-rendered prompt. `Deny` is styled
85
+ * `danger`; the first button `primary`.
86
+ */
87
+ export function buildSlackApprovalBlocks(specs) {
88
+ const elements = [];
89
+ for (const s of specs) {
90
+ const style = s.decision === "deny" ? "danger" : s.decision === "allow-once" ? "primary" : undefined;
91
+ const el = toButtonElement({ text: s.label, value: s.data, ...(style ? { style } : {}) }, SLACK_APPROVAL_ACTION_ID);
92
+ if (el)
93
+ elements.push(el);
94
+ }
95
+ if (elements.length < 2)
96
+ return []; // not enough buttons → caller uses text prompt
97
+ return chunkIntoActionBlocks(elements, "brigade_approval");
98
+ }
99
+ /**
100
+ * Build the `actions` blocks for a GENERAL inline keyboard from a grid of button
101
+ * specs. Each spec's `data` is prefixed with {@link GENERAL_CALLBACK_PREFIX} +
102
+ * sanitized; a button whose prefixed value exceeds the budget OR whose label is
103
+ * empty is DROPPED. Returns `null` when no usable button remains (the caller
104
+ * then sends a plain message instead). The grid is flattened + re-chunked into
105
+ * Slack's 5-per-block rows.
106
+ */
107
+ export function buildSlackInlineKeyboard(grid) {
108
+ const elements = [];
109
+ for (const row of grid) {
110
+ for (const spec of row) {
111
+ const label = (spec?.text ?? "").trim();
112
+ const token = spec?.data ?? "";
113
+ if (!label || !token)
114
+ continue;
115
+ const prefixed = `${GENERAL_CALLBACK_PREFIX}${token}`;
116
+ const el = toButtonElement({ text: label, value: prefixed }, SLACK_GENERAL_ACTION_ID);
117
+ if (el)
118
+ elements.push(el);
119
+ }
120
+ }
121
+ if (elements.length === 0)
122
+ return null;
123
+ return chunkIntoActionBlocks(elements, "brigade_general");
124
+ }
125
+ /**
126
+ * Pull the opaque codec payload out of a `block_actions` interaction's first
127
+ * Brigade-owned button action. Returns the `value` string the button declared
128
+ * (an approval-codec payload OR a general-prefixed token) so the adapter can
129
+ * stamp it onto `InboundMessage.callbackQuery.data` for the central pipeline to
130
+ * decode — exactly as Telegram surfaces `callback_query.data`. Returns null when
131
+ * no action carried one of our `action_id`s.
132
+ */
133
+ export function extractBlockActionPayload(actions) {
134
+ if (!Array.isArray(actions))
135
+ return null;
136
+ for (const a of actions) {
137
+ if (a?.action_id !== SLACK_APPROVAL_ACTION_ID && a?.action_id !== SLACK_GENERAL_ACTION_ID)
138
+ continue;
139
+ const value = typeof a.value === "string" ? a.value : "";
140
+ if (value)
141
+ return value;
142
+ }
143
+ return null;
144
+ }
145
+ //# sourceMappingURL=blocks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blocks.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/blocks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Slack slash-command helpers — normalize an inbound `/command` name + map
3
+ * Brigade's central channel commands to a documentable list.
4
+ *
5
+ * Brigade owns the command set centrally (`buildBundledCommands` → `/help`,
6
+ * `/status`, `/allowlist`, `/agent`, `/agents`, `/whoami`, `/org`, plus any
7
+ * module-registered channel commands). Telegram mirrors that set into the bot's
8
+ * `setMyCommands` menu on connect. SLACK IS DIFFERENT: slash commands are
9
+ * registered MANUALLY in the Slack app config UI (each `/command` points at the
10
+ * app's request URL / Socket Mode), so there is NO programmatic "set my
11
+ * commands" call. This module is therefore lighter than Telegram's — it provides:
12
+ *
13
+ * 1. {@link normalizeSlackCommandName} — canonicalise an inbound
14
+ * `slash_commands` event's `command` (e.g. `/Help` → `help`) so the
15
+ * adapter can match it against the central command map.
16
+ * 2. {@link buildSlackCommandManifest} — map the central commands to a plain
17
+ * `{ command, description }[]` the operator copies into the Slack app's
18
+ * slash-command config (surfaced in docs / `brigade channels status`).
19
+ *
20
+ * Pure / deterministic — no I/O. Output command names are printable ASCII
21
+ * (`[a-z0-9_-]`), so no NUL / control byte can appear.
22
+ */
23
+ import type { ChannelCommand } from "../sdk.js";
24
+ /** A Slack slash-command manifest entry (for the app-config UI / docs). */
25
+ export interface SlackSlashCommand {
26
+ /** Command WITHOUT the leading slash, e.g. `status`. */
27
+ command: string;
28
+ /** Short description shown in the Slack command hint. */
29
+ description: string;
30
+ }
31
+ /**
32
+ * Normalize a command word to Slack's `[a-z0-9_-]{1,32}` shape, or null if
33
+ * unusable. Strips a leading `/`, lowercases, and drops disallowed chars.
34
+ */
35
+ export declare function normalizeSlackCommandName(raw: string): string | null;
36
+ /**
37
+ * Build the Slack slash-command manifest from Brigade's central channel
38
+ * commands. De-dupes by normalized name (first wins), drops unusable names, and
39
+ * caps at 100. Returns `[]` when nothing maps. Unlike Telegram this is NOT
40
+ * pushed to Slack on connect — it's the list the operator registers by hand in
41
+ * the Slack app config (and what docs / status surface).
42
+ */
43
+ export declare function buildSlackCommandManifest(commands: ReadonlyArray<ChannelCommand>): SlackSlashCommand[];
44
+ //# sourceMappingURL=command-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-menu.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/command-menu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,2EAA2E;AAC3E,MAAM,WAAW,iBAAiB;IACjC,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;CACpB;AASD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKpE;AAQD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,aAAa,CAAC,cAAc,CAAC,GAAG,iBAAiB,EAAE,CAWtG"}