@spinabot/brigade 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +20 -1
  2. package/dist/agents/channels/bundled-channel-metas.d.ts +2 -0
  3. package/dist/agents/channels/bundled-channel-metas.d.ts.map +1 -1
  4. package/dist/agents/channels/bundled-channel-metas.js +11 -0
  5. package/dist/agents/channels/bundled-channel-metas.js.map +1 -1
  6. package/dist/agents/channels/manager.d.ts.map +1 -1
  7. package/dist/agents/channels/manager.js +18 -0
  8. package/dist/agents/channels/manager.js.map +1 -1
  9. package/dist/agents/channels/sdk.d.ts +2 -0
  10. package/dist/agents/channels/sdk.d.ts.map +1 -1
  11. package/dist/agents/channels/sdk.js +2 -0
  12. package/dist/agents/channels/sdk.js.map +1 -1
  13. package/dist/agents/channels/slack/account-config.d.ts +172 -0
  14. package/dist/agents/channels/slack/account-config.d.ts.map +1 -0
  15. package/dist/agents/channels/slack/account-config.js +353 -0
  16. package/dist/agents/channels/slack/account-config.js.map +1 -0
  17. package/dist/agents/channels/slack/account-registry.d.ts +45 -0
  18. package/dist/agents/channels/slack/account-registry.d.ts.map +1 -0
  19. package/dist/agents/channels/slack/account-registry.js +58 -0
  20. package/dist/agents/channels/slack/account-registry.js.map +1 -0
  21. package/dist/agents/channels/slack/adapter.d.ts +66 -0
  22. package/dist/agents/channels/slack/adapter.d.ts.map +1 -0
  23. package/dist/agents/channels/slack/adapter.js +547 -0
  24. package/dist/agents/channels/slack/adapter.js.map +1 -0
  25. package/dist/agents/channels/slack/approval-authorize.d.ts +43 -0
  26. package/dist/agents/channels/slack/approval-authorize.d.ts.map +1 -0
  27. package/dist/agents/channels/slack/approval-authorize.js +71 -0
  28. package/dist/agents/channels/slack/approval-authorize.js.map +1 -0
  29. package/dist/agents/channels/slack/approval-native.d.ts +70 -0
  30. package/dist/agents/channels/slack/approval-native.d.ts.map +1 -0
  31. package/dist/agents/channels/slack/approval-native.js +85 -0
  32. package/dist/agents/channels/slack/approval-native.js.map +1 -0
  33. package/dist/agents/channels/slack/blocks.d.ts +125 -0
  34. package/dist/agents/channels/slack/blocks.d.ts.map +1 -0
  35. package/dist/agents/channels/slack/blocks.js +145 -0
  36. package/dist/agents/channels/slack/blocks.js.map +1 -0
  37. package/dist/agents/channels/slack/command-menu.d.ts +44 -0
  38. package/dist/agents/channels/slack/command-menu.d.ts.map +1 -0
  39. package/dist/agents/channels/slack/command-menu.js +66 -0
  40. package/dist/agents/channels/slack/command-menu.js.map +1 -0
  41. package/dist/agents/channels/slack/connection.d.ts +422 -0
  42. package/dist/agents/channels/slack/connection.d.ts.map +1 -0
  43. package/dist/agents/channels/slack/connection.js +1042 -0
  44. package/dist/agents/channels/slack/connection.js.map +1 -0
  45. package/dist/agents/channels/slack/directory-live.d.ts +129 -0
  46. package/dist/agents/channels/slack/directory-live.d.ts.map +1 -0
  47. package/dist/agents/channels/slack/directory-live.js +148 -0
  48. package/dist/agents/channels/slack/directory-live.js.map +1 -0
  49. package/dist/agents/channels/slack/draft-stream.d.ts +93 -0
  50. package/dist/agents/channels/slack/draft-stream.d.ts.map +1 -0
  51. package/dist/agents/channels/slack/draft-stream.js +218 -0
  52. package/dist/agents/channels/slack/draft-stream.js.map +1 -0
  53. package/dist/agents/channels/slack/format.d.ts +41 -0
  54. package/dist/agents/channels/slack/format.d.ts.map +1 -0
  55. package/dist/agents/channels/slack/format.js +271 -0
  56. package/dist/agents/channels/slack/format.js.map +1 -0
  57. package/dist/agents/channels/slack/inbound-extras.d.ts +179 -0
  58. package/dist/agents/channels/slack/inbound-extras.d.ts.map +1 -0
  59. package/dist/agents/channels/slack/inbound-extras.js +257 -0
  60. package/dist/agents/channels/slack/inbound-extras.js.map +1 -0
  61. package/dist/agents/channels/slack/index.d.ts +15 -0
  62. package/dist/agents/channels/slack/index.d.ts.map +1 -0
  63. package/dist/agents/channels/slack/index.js +15 -0
  64. package/dist/agents/channels/slack/index.js.map +1 -0
  65. package/dist/agents/channels/slack/media.d.ts +90 -0
  66. package/dist/agents/channels/slack/media.d.ts.map +1 -0
  67. package/dist/agents/channels/slack/media.js +215 -0
  68. package/dist/agents/channels/slack/media.js.map +1 -0
  69. package/dist/agents/channels/slack/module.d.ts +26 -0
  70. package/dist/agents/channels/slack/module.d.ts.map +1 -0
  71. package/dist/agents/channels/slack/module.js +67 -0
  72. package/dist/agents/channels/slack/module.js.map +1 -0
  73. package/dist/agents/channels/slack/plugin.d.ts +69 -0
  74. package/dist/agents/channels/slack/plugin.d.ts.map +1 -0
  75. package/dist/agents/channels/slack/plugin.js +318 -0
  76. package/dist/agents/channels/slack/plugin.js.map +1 -0
  77. package/dist/agents/channels/slack/probe.d.ts +72 -0
  78. package/dist/agents/channels/slack/probe.d.ts.map +1 -0
  79. package/dist/agents/channels/slack/probe.js +103 -0
  80. package/dist/agents/channels/slack/probe.js.map +1 -0
  81. package/dist/agents/channels/slack/proxy-agent.d.ts +30 -0
  82. package/dist/agents/channels/slack/proxy-agent.d.ts.map +1 -0
  83. package/dist/agents/channels/slack/proxy-agent.js +44 -0
  84. package/dist/agents/channels/slack/proxy-agent.js.map +1 -0
  85. package/dist/agents/channels/slack/reasoning-lane.d.ts +42 -0
  86. package/dist/agents/channels/slack/reasoning-lane.d.ts.map +1 -0
  87. package/dist/agents/channels/slack/reasoning-lane.js +68 -0
  88. package/dist/agents/channels/slack/reasoning-lane.js.map +1 -0
  89. package/dist/agents/channels/slack/user-directory.d.ts +69 -0
  90. package/dist/agents/channels/slack/user-directory.d.ts.map +1 -0
  91. package/dist/agents/channels/slack/user-directory.js +94 -0
  92. package/dist/agents/channels/slack/user-directory.js.map +1 -0
  93. package/dist/agents/channels/slack/webhook.d.ts +89 -0
  94. package/dist/agents/channels/slack/webhook.d.ts.map +1 -0
  95. package/dist/agents/channels/slack/webhook.js +228 -0
  96. package/dist/agents/channels/slack/webhook.js.map +1 -0
  97. package/dist/agents/channels/telegram/adapter.d.ts.map +1 -1
  98. package/dist/agents/channels/telegram/adapter.js +10 -3
  99. package/dist/agents/channels/telegram/adapter.js.map +1 -1
  100. package/dist/agents/channels/telegram/connection.d.ts +10 -0
  101. package/dist/agents/channels/telegram/connection.d.ts.map +1 -1
  102. package/dist/agents/channels/telegram/connection.js +161 -5
  103. package/dist/agents/channels/telegram/connection.js.map +1 -1
  104. package/dist/agents/channels/telegram/format.d.ts +17 -0
  105. package/dist/agents/channels/telegram/format.d.ts.map +1 -1
  106. package/dist/agents/channels/telegram/format.js +53 -1
  107. package/dist/agents/channels/telegram/format.js.map +1 -1
  108. package/dist/agents/channels/telegram/inbound-extras.d.ts +17 -1
  109. package/dist/agents/channels/telegram/inbound-extras.d.ts.map +1 -1
  110. package/dist/agents/channels/telegram/inbound-extras.js +68 -7
  111. package/dist/agents/channels/telegram/inbound-extras.js.map +1 -1
  112. package/dist/agents/channels/telegram/media.d.ts +8 -0
  113. package/dist/agents/channels/telegram/media.d.ts.map +1 -1
  114. package/dist/agents/channels/telegram/media.js +30 -2
  115. package/dist/agents/channels/telegram/media.js.map +1 -1
  116. package/dist/agents/channels/telegram/webhook.d.ts.map +1 -1
  117. package/dist/agents/channels/telegram/webhook.js +7 -1
  118. package/dist/agents/channels/telegram/webhook.js.map +1 -1
  119. package/dist/agents/extensions/modules/index.d.ts.map +1 -1
  120. package/dist/agents/extensions/modules/index.js +5 -0
  121. package/dist/agents/extensions/modules/index.js.map +1 -1
  122. package/dist/agents/extensions/types.d.ts +11 -0
  123. package/dist/agents/extensions/types.d.ts.map +1 -1
  124. package/dist/agents/extensions/types.js.map +1 -1
  125. package/dist/buildstamp.json +1 -1
  126. package/dist/cli/commands/convex-cmd.d.ts +2 -1
  127. package/dist/cli/commands/convex-cmd.d.ts.map +1 -1
  128. package/dist/cli/commands/convex-cmd.js +79 -6
  129. package/dist/cli/commands/convex-cmd.js.map +1 -1
  130. package/dist/cli/program/build-program.js +1 -1
  131. package/dist/cli/program/build-program.js.map +1 -1
  132. package/dist/core/server.d.ts.map +1 -1
  133. package/dist/core/server.js +24 -5
  134. package/dist/core/server.js.map +1 -1
  135. package/package.json +4 -1
  136. package/scripts/convex-dev.mjs +28 -2
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Pure extractors that turn a Slack Events-API event payload into the
3
+ * normalized fields Brigade's `InboundMessage` carries. No network, no side
4
+ * effects — every function here is deterministic over its event argument so
5
+ * they're trivial to unit-test without a live workspace.
6
+ *
7
+ * Slack's wire shape differs from Telegram's in load-bearing ways, so the logic
8
+ * is a Brigade-native re-implementation that models the SHAPE of
9
+ * `telegram/inbound-extras.ts` (raw event → normalized signals) while speaking
10
+ * Slack semantics:
11
+ *
12
+ * - Text arrives as `event.text` peppered with Slack mention/link TOKENS:
13
+ * `<@U123>` (user), `<#C123|name>` (channel), `<https://x|label>` (link),
14
+ * `<!here>` / `<!subteam^S1|@team>` (special). {@link extractSlackText}
15
+ * expands those into readable plain text the agent can parse.
16
+ * - The bot is "addressed" when its own `user_id` appears as a `<@Uxxx>`
17
+ * mention (or the event is an `app_mention`). {@link extractSlackMentions}
18
+ * surfaces the bot's id when addressed so the central group ACL admits the
19
+ * message — exactly as Telegram surfaces the bot's numeric id.
20
+ * - Channel kind is read from `event.channel_type` (`im` → direct;
21
+ * `mpim`/`channel`/`group` → group), see {@link slackChannelType}.
22
+ * - Threads ride on `thread_ts`; a reply quotes its parent.
23
+ * - Files arrive as `event.files[]` (each a `url_private` + metadata); the
24
+ * connection layer DEFERS the byte download until the access gate admits
25
+ * the sender (mirrors Telegram's deferred-media discipline).
26
+ *
27
+ * Brigade's CENTRAL inbound pipeline owns the actual ACL / mention / routing
28
+ * decision — these helpers only surface the raw signals it reads (`mentions`,
29
+ * `threadId`, `replyTo`, chat type).
30
+ */
31
+ import type { InboundReplyContext } from "../sdk.js";
32
+ /** One file object Slack attaches to a message event (the subset we read). */
33
+ export interface SlackFileObject {
34
+ id?: string;
35
+ name?: string;
36
+ title?: string;
37
+ mimetype?: string;
38
+ filetype?: string;
39
+ /** Authenticated download URL — requires `Authorization: Bearer <botToken>`. */
40
+ url_private?: string;
41
+ url_private_download?: string;
42
+ size?: number;
43
+ /** Slack marks a file `mode: "tombstone"` when it was deleted / is unavailable. */
44
+ mode?: string;
45
+ }
46
+ /**
47
+ * A Slack message event (the `message` / `app_mention` family). Only the fields
48
+ * Brigade consumes are typed; the raw event carries far more. `subtype`
49
+ * discriminates edits (`message_changed`), deletes (`message_deleted`), and the
50
+ * many bot/system message variants we filter out.
51
+ */
52
+ export interface SlackMessageEvent {
53
+ type?: string;
54
+ subtype?: string;
55
+ /** Sender user id (`U…` / `W…`). Absent on some system subtypes. */
56
+ user?: string;
57
+ /** Bot id when the message was posted by a bot integration (not a user). */
58
+ bot_id?: string;
59
+ text?: string;
60
+ /** Channel id (`C…` public, `G…` private, `D…` DM). */
61
+ channel?: string;
62
+ /** `im` (DM) | `mpim` (group DM) | `channel` (public) | `group` (private). */
63
+ channel_type?: string;
64
+ /** Message timestamp — Slack's per-message id within a channel. */
65
+ ts?: string;
66
+ /** Parent thread ts when this message belongs to a thread. */
67
+ thread_ts?: string;
68
+ /** Client-generated id — a stable dedupe key across redeliveries. */
69
+ client_msg_id?: string;
70
+ /**
71
+ * Edit stamp Slack sets on an edited message (`{ user, ts }` where `ts` is
72
+ * WHEN the edit happened, distinct from the message's own `ts`). Folded into
73
+ * the dedupe key so a SECOND edit of the same message isn't dropped.
74
+ */
75
+ edited?: {
76
+ ts?: string;
77
+ user?: string;
78
+ };
79
+ /** Files attached to the message. */
80
+ files?: SlackFileObject[];
81
+ /** The edited / changed message envelope (subtype `message_changed`). */
82
+ message?: SlackMessageEvent;
83
+ /** The prior message before an edit (subtype `message_changed`). */
84
+ previous_message?: SlackMessageEvent;
85
+ /** The deleted message's ts (subtype `message_deleted`). */
86
+ deleted_ts?: string;
87
+ [key: string]: unknown;
88
+ }
89
+ /** A Slack `reaction_added` / `reaction_removed` event (the subset we read). */
90
+ export interface SlackReactionEvent {
91
+ type?: string;
92
+ /** The user who added / removed the reaction. */
93
+ user?: string;
94
+ /** The emoji name (no colons), e.g. `thumbsup`. */
95
+ reaction?: string;
96
+ /** The message the reaction landed on. */
97
+ item?: {
98
+ type?: string;
99
+ channel?: string;
100
+ ts?: string;
101
+ };
102
+ [key: string]: unknown;
103
+ }
104
+ /**
105
+ * Reverse Slack's text-node entity escaping (`&amp;` `&lt;` `&gt;`). Slack
106
+ * escapes only those three in message text; everything else is literal. Applied
107
+ * AFTER token expansion so the angle-bracket tokens (`<…>`) parse first.
108
+ */
109
+ export declare function unescapeSlackEntities(text: string): string;
110
+ /**
111
+ * Expand the Slack message TOKENS in a run of text into readable plain text:
112
+ * - `<@U123>` / `<@U123|alex>` → `@alex` (or `@U123` when no label)
113
+ * - `<#C123|general>` / `<#C123>` → `#general` (or `#C123`)
114
+ * - `<!here>` / `<!channel>` / `<!everyone>` → `@here` / `@channel` / `@everyone`
115
+ * - `<!subteam^S1|@team>` → `@team`
116
+ * - `<!date^…^fallback|link>` → the fallback text
117
+ * - `<https://x|label>` / `<https://x>` → `label` (or the bare url)
118
+ *
119
+ * Unknown `<…>` tokens collapse to their inner display text (after `|`) or are
120
+ * stripped of the angle brackets. Pure + deterministic.
121
+ */
122
+ export declare function expandSlackTokens(text: string, resolveName?: (id: string) => string | undefined): string;
123
+ /**
124
+ * The agent-facing plain text of a Slack message. Token-expanded (mentions /
125
+ * channels / links → readable text) then entity-unescaped. A `message_changed`
126
+ * envelope surfaces the EDITED text (`event.message.text`). Binary blobs are
127
+ * dropped to "".
128
+ */
129
+ export declare function extractSlackText(event: SlackMessageEvent, resolveName?: (id: string) => string | undefined): string;
130
+ /** Slack `channel_type` → Brigade chat type. `im` → direct; everything else → group. */
131
+ export declare function slackChannelType(event: Pick<SlackMessageEvent, "channel_type" | "channel">): "direct" | "group";
132
+ /** Thread parent ts as a string, when the message belongs to a thread. */
133
+ export declare function slackThreadId(event: Pick<SlackMessageEvent, "thread_ts">): string | undefined;
134
+ /**
135
+ * Channel-native ids of accounts addressed in this message. Brigade's central
136
+ * group ACL treats a group message as "addressed to the bot" when the bot's own
137
+ * id appears in `mentions`; without this a group message never reaches the
138
+ * agent. So when the bot's own `<@Uxxx>` mention appears in the text — OR the
139
+ * event is an `app_mention` (Slack's dedicated "the bot was @-mentioned" event)
140
+ * — we surface the bot's user id (passed in from `auth.test`). Every OTHER
141
+ * `<@Uxxx>` user mention is surfaced too so the pipeline sees who else was
142
+ * tagged.
143
+ *
144
+ * @param botUserId the bot's own user id (from `auth.test`), surfaced when the
145
+ * bot is @-mentioned so the ACL admits the group message.
146
+ */
147
+ export declare function extractSlackMentions(event: SlackMessageEvent, botUserId?: string): string[];
148
+ /**
149
+ * A short display name for the sender. A legacy bot-posted `username` wins; else
150
+ * a resolved display name (when a `resolveName` reader from the user directory is
151
+ * supplied AND it has already cached the sender's name); else the raw user id
152
+ * (`U…`) — display-name resolution needs a `users.info` call that the directory
153
+ * primes in the background, so the first message from a never-seen user surfaces
154
+ * the id and later messages surface the name.
155
+ */
156
+ export declare function buildSlackSenderName(event: SlackMessageEvent, resolveName?: (id: string) => string | undefined): string | undefined;
157
+ /**
158
+ * Reply-context (what message this inbound quotes), when it's a threaded reply.
159
+ * Slack threads are flat — a reply carries `thread_ts` (the parent's ts) but the
160
+ * event does NOT inline the parent's text — so the context surfaces the parent
161
+ * message id (`thread_ts`) and leaves `body` undefined (the pipeline can fetch
162
+ * the parent if it needs the excerpt). Returns undefined for a top-level
163
+ * message (no `thread_ts`, or `thread_ts === ts` which is the thread ROOT, not a
164
+ * reply).
165
+ */
166
+ export declare function extractSlackReplyContext(event: SlackMessageEvent): InboundReplyContext | undefined;
167
+ /**
168
+ * Cheap presence probe — does this message carry a downloadable file? Walks
169
+ * `event.files[]` (an edit reads the nested message's files) but never touches
170
+ * the network, so the connection layer can DEFER the actual download until AFTER
171
+ * the central access gate admits the sender (mirrors Telegram's deferred-media
172
+ * discipline). Mirrors `hasInboundMedia`.
173
+ */
174
+ export declare function hasInboundMedia(event: SlackMessageEvent): boolean;
175
+ /** The list of downloadable files on a message (an edit reads the nested message). */
176
+ export declare function resolveInboundFiles(event: SlackMessageEvent): SlackFileObject[];
177
+ /** The Brigade media-kind of a Slack file, from its mimetype / filetype. */
178
+ export declare function resolveSlackFileKind(f: SlackFileObject): "image" | "video" | "audio" | "voice" | "document";
179
+ //# sourceMappingURL=inbound-extras.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-extras.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/inbound-extras.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Pure extractors that turn a Slack Events-API event payload into the
3
+ * normalized fields Brigade's `InboundMessage` carries. No network, no side
4
+ * effects — every function here is deterministic over its event argument so
5
+ * they're trivial to unit-test without a live workspace.
6
+ *
7
+ * Slack's wire shape differs from Telegram's in load-bearing ways, so the logic
8
+ * is a Brigade-native re-implementation that models the SHAPE of
9
+ * `telegram/inbound-extras.ts` (raw event → normalized signals) while speaking
10
+ * Slack semantics:
11
+ *
12
+ * - Text arrives as `event.text` peppered with Slack mention/link TOKENS:
13
+ * `<@U123>` (user), `<#C123|name>` (channel), `<https://x|label>` (link),
14
+ * `<!here>` / `<!subteam^S1|@team>` (special). {@link extractSlackText}
15
+ * expands those into readable plain text the agent can parse.
16
+ * - The bot is "addressed" when its own `user_id` appears as a `<@Uxxx>`
17
+ * mention (or the event is an `app_mention`). {@link extractSlackMentions}
18
+ * surfaces the bot's id when addressed so the central group ACL admits the
19
+ * message — exactly as Telegram surfaces the bot's numeric id.
20
+ * - Channel kind is read from `event.channel_type` (`im` → direct;
21
+ * `mpim`/`channel`/`group` → group), see {@link slackChannelType}.
22
+ * - Threads ride on `thread_ts`; a reply quotes its parent.
23
+ * - Files arrive as `event.files[]` (each a `url_private` + metadata); the
24
+ * connection layer DEFERS the byte download until the access gate admits
25
+ * the sender (mirrors Telegram's deferred-media discipline).
26
+ *
27
+ * Brigade's CENTRAL inbound pipeline owns the actual ACL / mention / routing
28
+ * decision — these helpers only surface the raw signals it reads (`mentions`,
29
+ * `threadId`, `replyTo`, chat type).
30
+ */
31
+ /**
32
+ * Reject control-byte payloads (a binary blob masquerading as text). Tab / LF /
33
+ * CR are allowed; any other C0 control char marks the run as non-text and it's
34
+ * dropped so the agent never ingests raw binary. Mirrors Telegram's
35
+ * `isBinaryContent`.
36
+ */
37
+ function isBinaryContent(text) {
38
+ for (let i = 0; i < text.length; i++) {
39
+ const code = text.charCodeAt(i);
40
+ if (code <= 0x1f && code !== 0x09 && code !== 0x0a && code !== 0x0d) {
41
+ return true;
42
+ }
43
+ }
44
+ return false;
45
+ }
46
+ /**
47
+ * Reverse Slack's text-node entity escaping (`&amp;` `&lt;` `&gt;`). Slack
48
+ * escapes only those three in message text; everything else is literal. Applied
49
+ * AFTER token expansion so the angle-bracket tokens (`<…>`) parse first.
50
+ */
51
+ export function unescapeSlackEntities(text) {
52
+ return text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
53
+ }
54
+ /**
55
+ * Expand the Slack message TOKENS in a run of text into readable plain text:
56
+ * - `<@U123>` / `<@U123|alex>` → `@alex` (or `@U123` when no label)
57
+ * - `<#C123|general>` / `<#C123>` → `#general` (or `#C123`)
58
+ * - `<!here>` / `<!channel>` / `<!everyone>` → `@here` / `@channel` / `@everyone`
59
+ * - `<!subteam^S1|@team>` → `@team`
60
+ * - `<!date^…^fallback|link>` → the fallback text
61
+ * - `<https://x|label>` / `<https://x>` → `label` (or the bare url)
62
+ *
63
+ * Unknown `<…>` tokens collapse to their inner display text (after `|`) or are
64
+ * stripped of the angle brackets. Pure + deterministic.
65
+ */
66
+ export function expandSlackTokens(text, resolveName) {
67
+ return text.replace(/<([^>]*)>/g, (_whole, inner) => {
68
+ if (!inner)
69
+ return "";
70
+ const bar = inner.indexOf("|");
71
+ const head = bar === -1 ? inner : inner.slice(0, bar);
72
+ const label = bar === -1 ? "" : inner.slice(bar + 1);
73
+ const first = head[0];
74
+ // User mention: <@U123> / <@U123|alex>
75
+ if (first === "@") {
76
+ const id = head.slice(1);
77
+ // Inline label wins; else a resolved display name (from the user
78
+ // directory, when one is supplied); else the bare id.
79
+ if (label)
80
+ return `@${label}`;
81
+ const resolved = resolveName?.(id);
82
+ return resolved ? `@${resolved}` : `@${id}`;
83
+ }
84
+ // Channel mention: <#C123|general> / <#C123>
85
+ if (first === "#") {
86
+ const id = head.slice(1);
87
+ return label ? `#${label}` : `#${id}`;
88
+ }
89
+ // Special mention / subteam / date: <!here> / <!subteam^S1|@team> / <!date^…|link>
90
+ if (first === "!") {
91
+ const body = head.slice(1);
92
+ if (label)
93
+ return label.startsWith("@") ? label : `@${label}`;
94
+ // <!here> / <!channel> / <!everyone> → @here etc.; <!subteam^S1> → @team-ish
95
+ const name = body.split("^")[0] ?? body;
96
+ return `@${name}`;
97
+ }
98
+ // Link: <https://x|label> / <mailto:a@b|a@b> / <https://x>
99
+ return label || head;
100
+ });
101
+ }
102
+ /**
103
+ * The agent-facing plain text of a Slack message. Token-expanded (mentions /
104
+ * channels / links → readable text) then entity-unescaped. A `message_changed`
105
+ * envelope surfaces the EDITED text (`event.message.text`). Binary blobs are
106
+ * dropped to "".
107
+ */
108
+ export function extractSlackText(event, resolveName) {
109
+ // An edit (message_changed) carries the new text on the nested `message`.
110
+ const source = event.subtype === "message_changed" && event.message ? event.message : event;
111
+ const raw = typeof source.text === "string" ? source.text : "";
112
+ if (!raw || isBinaryContent(raw))
113
+ return "";
114
+ return unescapeSlackEntities(expandSlackTokens(raw, resolveName)).trim();
115
+ }
116
+ /** Slack `channel_type` → Brigade chat type. `im` → direct; everything else → group. */
117
+ export function slackChannelType(event) {
118
+ const t = typeof event.channel_type === "string" ? event.channel_type : "";
119
+ if (t === "im")
120
+ return "direct";
121
+ if (t === "mpim" || t === "channel" || t === "group")
122
+ return "group";
123
+ // Fall back to the channel-id prefix when `channel_type` was omitted: a `D…`
124
+ // id is a DM, anything else is a multi-party room.
125
+ const ch = typeof event.channel === "string" ? event.channel : "";
126
+ return ch.startsWith("D") ? "direct" : "group";
127
+ }
128
+ /** Thread parent ts as a string, when the message belongs to a thread. */
129
+ export function slackThreadId(event) {
130
+ const ts = event.thread_ts;
131
+ return typeof ts === "string" && ts ? ts : undefined;
132
+ }
133
+ /**
134
+ * Channel-native ids of accounts addressed in this message. Brigade's central
135
+ * group ACL treats a group message as "addressed to the bot" when the bot's own
136
+ * id appears in `mentions`; without this a group message never reaches the
137
+ * agent. So when the bot's own `<@Uxxx>` mention appears in the text — OR the
138
+ * event is an `app_mention` (Slack's dedicated "the bot was @-mentioned" event)
139
+ * — we surface the bot's user id (passed in from `auth.test`). Every OTHER
140
+ * `<@Uxxx>` user mention is surfaced too so the pipeline sees who else was
141
+ * tagged.
142
+ *
143
+ * @param botUserId the bot's own user id (from `auth.test`), surfaced when the
144
+ * bot is @-mentioned so the ACL admits the group message.
145
+ */
146
+ export function extractSlackMentions(event, botUserId) {
147
+ const source = event.subtype === "message_changed" && event.message ? event.message : event;
148
+ const text = typeof source.text === "string" ? source.text : "";
149
+ const out = [];
150
+ const seen = new Set();
151
+ const push = (id) => {
152
+ if (!id || seen.has(id))
153
+ return;
154
+ seen.add(id);
155
+ out.push(id);
156
+ };
157
+ let botMentioned = event.type === "app_mention";
158
+ // Scan every <@U…> / <@U…|label> user-mention token in the text.
159
+ const re = /<@([A-Z0-9]+)(?:\|[^>]*)?>/g;
160
+ let m;
161
+ while ((m = re.exec(text)) !== null) {
162
+ const id = m[1];
163
+ if (!id)
164
+ continue;
165
+ if (botUserId && id === botUserId) {
166
+ botMentioned = true;
167
+ continue; // the bot's own id is pushed last (after the addressed check)
168
+ }
169
+ push(id);
170
+ }
171
+ if (botMentioned && botUserId)
172
+ push(botUserId);
173
+ return out;
174
+ }
175
+ /**
176
+ * A short display name for the sender. A legacy bot-posted `username` wins; else
177
+ * a resolved display name (when a `resolveName` reader from the user directory is
178
+ * supplied AND it has already cached the sender's name); else the raw user id
179
+ * (`U…`) — display-name resolution needs a `users.info` call that the directory
180
+ * primes in the background, so the first message from a never-seen user surfaces
181
+ * the id and later messages surface the name.
182
+ */
183
+ export function buildSlackSenderName(event, resolveName) {
184
+ const source = event.subtype === "message_changed" && event.message ? event.message : event;
185
+ const username = typeof source["username"] === "string" ? source["username"] : "";
186
+ if (username)
187
+ return username;
188
+ const user = typeof source.user === "string" ? source.user : "";
189
+ if (user) {
190
+ const resolved = resolveName?.(user);
191
+ if (resolved)
192
+ return resolved;
193
+ }
194
+ return user || undefined;
195
+ }
196
+ /**
197
+ * Reply-context (what message this inbound quotes), when it's a threaded reply.
198
+ * Slack threads are flat — a reply carries `thread_ts` (the parent's ts) but the
199
+ * event does NOT inline the parent's text — so the context surfaces the parent
200
+ * message id (`thread_ts`) and leaves `body` undefined (the pipeline can fetch
201
+ * the parent if it needs the excerpt). Returns undefined for a top-level
202
+ * message (no `thread_ts`, or `thread_ts === ts` which is the thread ROOT, not a
203
+ * reply).
204
+ */
205
+ export function extractSlackReplyContext(event) {
206
+ const source = event.subtype === "message_changed" && event.message ? event.message : event;
207
+ const threadTs = source.thread_ts;
208
+ const ts = source.ts;
209
+ if (typeof threadTs !== "string" || !threadTs)
210
+ return undefined;
211
+ // The thread ROOT carries thread_ts === ts; only a genuine reply quotes a
212
+ // DIFFERENT parent.
213
+ if (typeof ts === "string" && ts === threadTs)
214
+ return undefined;
215
+ return { messageId: threadTs };
216
+ }
217
+ /* ───────────────────────── media detection ───────────────────────── */
218
+ /** A downloadable file is one with a private url that isn't a tombstone (deleted). */
219
+ function isDownloadableFile(f) {
220
+ if (!f || f.mode === "tombstone")
221
+ return false;
222
+ return Boolean(f.url_private || f.url_private_download);
223
+ }
224
+ /**
225
+ * Cheap presence probe — does this message carry a downloadable file? Walks
226
+ * `event.files[]` (an edit reads the nested message's files) but never touches
227
+ * the network, so the connection layer can DEFER the actual download until AFTER
228
+ * the central access gate admits the sender (mirrors Telegram's deferred-media
229
+ * discipline). Mirrors `hasInboundMedia`.
230
+ */
231
+ export function hasInboundMedia(event) {
232
+ const source = event.subtype === "message_changed" && event.message ? event.message : event;
233
+ const files = Array.isArray(source.files) ? source.files : [];
234
+ return files.some(isDownloadableFile);
235
+ }
236
+ /** The list of downloadable files on a message (an edit reads the nested message). */
237
+ export function resolveInboundFiles(event) {
238
+ const source = event.subtype === "message_changed" && event.message ? event.message : event;
239
+ const files = Array.isArray(source.files) ? source.files : [];
240
+ return files.filter(isDownloadableFile);
241
+ }
242
+ /** The Brigade media-kind of a Slack file, from its mimetype / filetype. */
243
+ export function resolveSlackFileKind(f) {
244
+ const mime = (f.mimetype ?? "").toLowerCase();
245
+ const type = (f.filetype ?? "").toLowerCase();
246
+ if (mime.startsWith("image/") || /^(png|jpg|jpeg|gif|webp|bmp|heic)$/.test(type))
247
+ return "image";
248
+ if (mime.startsWith("video/") || /^(mp4|mov|webm|mkv|avi)$/.test(type))
249
+ return "video";
250
+ // Slack voice memos arrive as audio with a dedicated subtype; treat m4a/ogg
251
+ // voice-ish containers as "voice", other audio as "audio".
252
+ if (mime.startsWith("audio/") || /^(mp3|m4a|ogg|wav|flac|aac)$/.test(type)) {
253
+ return type === "m4a" || type === "ogg" ? "voice" : "audio";
254
+ }
255
+ return "document";
256
+ }
257
+ //# sourceMappingURL=inbound-extras.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbound-extras.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/inbound-extras.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /** Slack channel — public surface. */
2
+ export { createSlackAdapter, buildReactionNote, SLACK_CAPABILITIES, type CreateSlackAdapterOptions, type SlackAdapter, } from "./adapter.js";
3
+ export { listSlackAccountIds, resolveSlackAccount, resolveSlackBotToken, resolveSlackAppToken, resolveSlackSigningSecret, resolveSlackUserToken, slackChannelEnabled, slackEventsConfig, slackLiveStreamEnabled, slackStreamThrottleMs, slackSurfaceReasoning, slackThreadIdleTtlMs, SLACK_BOT_TOKEN_ENV_VAR, SLACK_APP_TOKEN_ENV_VAR, SLACK_SIGNING_SECRET_ENV_VAR, SLACK_CHANNEL_ID, SLACK_DEFAULT_ACCOUNT_ID, type ResolvedSlackAccount, type SlackEventsConfig, } from "./account-config.js";
4
+ export { buildSlackApprovalMessage, buildSlackApprovalText, parseSlackApprovalAction, type SlackApprovalMessage, } from "./approval-native.js";
5
+ export { resolveSlackApprover } from "./approval-authorize.js";
6
+ export { buildSlackApprovalBlocks, buildSlackInlineKeyboard, extractBlockActionPayload, SLACK_APPROVAL_ACTION_ID, SLACK_GENERAL_ACTION_ID, type SlackActionsBlock, type SlackBlock, } from "./blocks.js";
7
+ export { buildSlackCommandManifest, normalizeSlackCommandName, type SlackSlashCommand, } from "./command-menu.js";
8
+ export { connectSlack, isSlackUnauthorized, redactSlackToken, slackBackoffDelay, type ConnectSlackArgs, type SlackConnection, type SlackInboundMessage, } from "./connection.js";
9
+ export { listSlackDirectoryPeers, listSlackDirectoryGroups, type SlackDirectoryEntry, type SlackDirectoryQuery, type SlackDirectoryWebClientLike, } from "./directory-live.js";
10
+ export { markdownToSlackMrkdwn, slackMrkdwnIsEmpty, escapeSlackMrkdwn } from "./format.js";
11
+ export { createSlackPlugin, type SlackPluginDeps, type SlackPluginHandle } from "./plugin.js";
12
+ export { probeSlack, type SlackProbeResult, type SlackProbeBot, type SlackProbeTeam } from "./probe.js";
13
+ export { buildSlackWebhookRoute, verifySlackSignature, parseSlackBody, SLACK_SIGNATURE_HEADER, SLACK_TIMESTAMP_HEADER, } from "./webhook.js";
14
+ export { slackModule } from "./module.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC,OAAO,EACN,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,yBAAyB,EAC9B,KAAK,YAAY,GACjB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,yBAAyB,EACzB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,EACvB,4BAA4B,EAC5B,gBAAgB,EAChB,wBAAwB,EACxB,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACN,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,EACxB,KAAK,oBAAoB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EACN,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,EACvB,KAAK,iBAAiB,EACtB,KAAK,UAAU,GACf,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,yBAAyB,EACzB,yBAAyB,EACzB,KAAK,iBAAiB,GACtB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,mBAAmB,GACxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACN,uBAAuB,EACvB,wBAAwB,EACxB,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,2BAA2B,GAChC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC9F,OAAO,EAAE,UAAU,EAAE,KAAK,gBAAgB,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AACxG,OAAO,EACN,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,sBAAsB,GACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,15 @@
1
+ /** Slack channel — public surface. */
2
+ export { createSlackAdapter, buildReactionNote, SLACK_CAPABILITIES, } from "./adapter.js";
3
+ export { listSlackAccountIds, resolveSlackAccount, resolveSlackBotToken, resolveSlackAppToken, resolveSlackSigningSecret, resolveSlackUserToken, slackChannelEnabled, slackEventsConfig, slackLiveStreamEnabled, slackStreamThrottleMs, slackSurfaceReasoning, slackThreadIdleTtlMs, SLACK_BOT_TOKEN_ENV_VAR, SLACK_APP_TOKEN_ENV_VAR, SLACK_SIGNING_SECRET_ENV_VAR, SLACK_CHANNEL_ID, SLACK_DEFAULT_ACCOUNT_ID, } from "./account-config.js";
4
+ export { buildSlackApprovalMessage, buildSlackApprovalText, parseSlackApprovalAction, } from "./approval-native.js";
5
+ export { resolveSlackApprover } from "./approval-authorize.js";
6
+ export { buildSlackApprovalBlocks, buildSlackInlineKeyboard, extractBlockActionPayload, SLACK_APPROVAL_ACTION_ID, SLACK_GENERAL_ACTION_ID, } from "./blocks.js";
7
+ export { buildSlackCommandManifest, normalizeSlackCommandName, } from "./command-menu.js";
8
+ export { connectSlack, isSlackUnauthorized, redactSlackToken, slackBackoffDelay, } from "./connection.js";
9
+ export { listSlackDirectoryPeers, listSlackDirectoryGroups, } from "./directory-live.js";
10
+ export { markdownToSlackMrkdwn, slackMrkdwnIsEmpty, escapeSlackMrkdwn } from "./format.js";
11
+ export { createSlackPlugin } from "./plugin.js";
12
+ export { probeSlack } from "./probe.js";
13
+ export { buildSlackWebhookRoute, verifySlackSignature, parseSlackBody, SLACK_SIGNATURE_HEADER, SLACK_TIMESTAMP_HEADER, } from "./webhook.js";
14
+ export { slackModule } from "./module.js";
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/agents/channels/slack/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AAEtC,OAAO,EACN,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GAGlB,MAAM,cAAc,CAAC;AACtB,OAAO,EACN,mBAAmB,EACnB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,yBAAyB,EACzB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,EACvB,4BAA4B,EAC5B,gBAAgB,EAChB,wBAAwB,GAGxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACN,yBAAyB,EACzB,sBAAsB,EACtB,wBAAwB,GAExB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EACN,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACxB,uBAAuB,GAGvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,yBAAyB,EACzB,yBAAyB,GAEzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,YAAY,EACZ,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,GAIjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACN,uBAAuB,EACvB,wBAAwB,GAIxB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,iBAAiB,EAAgD,MAAM,aAAa,CAAC;AAC9F,OAAO,EAAE,UAAU,EAAkE,MAAM,YAAY,CAAC;AACxG,OAAO,EACN,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,sBAAsB,GACtB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Slack media helpers — inbound download + outbound upload construction.
3
+ *
4
+ * INBOUND: Slack doesn't push file bytes; a message event carries file objects
5
+ * with an authenticated `url_private`. To get the bytes we GET that URL with an
6
+ * `Authorization: Bearer <botToken>` header (a plain fetch returns the login
7
+ * HTML otherwise). Bytes are saved under
8
+ * `~/.brigade/channels/slack/media/<YYYY-MM-DD>/<fileId>.<ext>` so the agent can
9
+ * `read` the attachment by path. In convex mode the cache relocates to the OS
10
+ * cache dir (never under ~/.brigade, to respect the strict-zero guard).
11
+ *
12
+ * OUTBOUND: `uploadSlackFile` posts a local path's bytes via the Web API's
13
+ * `files.uploadV2`, after running the path through Brigade's outbound media-path
14
+ * guard so a prompt-injected "send ~/.ssh/id_rsa" can't exfiltrate a secret. The
15
+ * `@slack/web-api` `WebClient` is injected (not imported here) so this module
16
+ * stays dependency-light + unit-testable.
17
+ */
18
+ import { type InboundMediaAttachment, type OutboundMedia } from "../sdk.js";
19
+ import { type SlackFileObject } from "./inbound-extras.js";
20
+ /**
21
+ * Defensive ceiling on an inbound file download. Slack's own per-file limit is
22
+ * generous; we cap at 50 MB so a huge upload can't blow out memory. Anything
23
+ * larger is skipped (the message still reaches the agent without the
24
+ * attachment).
25
+ */
26
+ declare const MAX_BYTES: number;
27
+ /**
28
+ * True when `rawUrl` is an https URL whose host is a Slack file-CDN host (or a
29
+ * subdomain of one). Anything else (non-https, a non-Slack host, or an
30
+ * unparseable URL) returns false so the caller refuses to fetch with the token.
31
+ */
32
+ export declare function isAllowedSlackFileUrl(rawUrl: string): boolean;
33
+ /**
34
+ * Bounded retry for the transient file fetch. Slack's file CDN occasionally
35
+ * blips on a 5xx or a network reset; one or two quick retries turn a dropped
36
+ * attachment into a delivered one. The caller still wraps the whole thing in a
37
+ * try/catch that degrades to `null`, so an exhausted retry never breaks message
38
+ * delivery. Mirrors Telegram's `withMediaRetry`.
39
+ */
40
+ export declare function withSlackRetry<T>(fn: () => Promise<T>, attempts?: number): Promise<T>;
41
+ export interface DownloadSlackFileArgs {
42
+ /** The Slack file object (from the message event's `files[]`). */
43
+ file: SlackFileObject;
44
+ /** Bot (or user) token — sent as `Authorization: Bearer …`. NEVER logged. */
45
+ token: string;
46
+ /** Injectable fetch (defaults to global fetch) — lets tests stub the download. */
47
+ fetchImpl?: typeof fetch;
48
+ /** Logger so a failed download logs without crashing the inbound flow. */
49
+ log?: (msg: string, meta?: Record<string, unknown>) => void;
50
+ }
51
+ /**
52
+ * Download one inbound Slack file to disk and return its normalized descriptor,
53
+ * or `null` when the file couldn't be fetched (no url / too big / network error
54
+ * / tombstoned). Never throws — a download glitch must not break message
55
+ * delivery. The `Authorization: Bearer` header is REQUIRED; a plain GET of
56
+ * `url_private` returns Slack's HTML login page, not the bytes.
57
+ */
58
+ export declare function downloadSlackFile(args: DownloadSlackFileArgs): Promise<InboundMediaAttachment | null>;
59
+ /** The minimal `files.uploadV2` surface the outbound path drives — injectable for tests. */
60
+ export interface SlackUploadApi {
61
+ files: {
62
+ uploadV2(args: {
63
+ channel_id: string;
64
+ file: unknown;
65
+ filename: string;
66
+ initial_comment?: string;
67
+ thread_ts?: string;
68
+ }): Promise<unknown>;
69
+ };
70
+ }
71
+ export interface UploadSlackFileArgs {
72
+ /** The Web API client (`WebClient`) the upload runs through. */
73
+ client: SlackUploadApi;
74
+ /** Destination channel id. */
75
+ channelId: string;
76
+ /** The local media to upload. */
77
+ media: OutboundMedia;
78
+ /** Optional thread to upload into. */
79
+ threadId?: string;
80
+ }
81
+ /**
82
+ * Upload a local file (image / video / audio / doc) to a Slack channel via
83
+ * `files.uploadV2`, after running the path through Brigade's outbound
84
+ * media-path guard. Throws a clear operator-facing error when the guard refuses
85
+ * the path (the `send_media` tool surfaces it). The file is streamed from disk;
86
+ * the caption rides as `initial_comment`.
87
+ */
88
+ export declare function uploadSlackFile(args: UploadSlackFileArgs): Promise<void>;
89
+ export { MAX_BYTES as SLACK_MEDIA_MAX_BYTES };
90
+ //# sourceMappingURL=media.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/slack/media.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAUH,OAAO,EAEN,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAwB,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAIjF;;;;;GAKG;AACH,QAAA,MAAM,SAAS,QAAmB,CAAC;AAYnC;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAU7D;AAqCD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,SAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAWtF;AAED,MAAM,WAAW,qBAAqB;IACrC,kEAAkE;IAClE,IAAI,EAAE,eAAe,CAAC;IACtB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;IACd,kFAAkF;IAClF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAC5D;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAgE3G;AAED,4FAA4F;AAC5F,MAAM,WAAW,cAAc;IAC9B,KAAK,EAAE;QACN,QAAQ,CAAC,IAAI,EAAE;YACd,UAAU,EAAE,MAAM,CAAC;YACnB,IAAI,EAAE,OAAO,CAAC;YACd,QAAQ,EAAE,MAAM,CAAC;YACjB,eAAe,CAAC,EAAE,MAAM,CAAC;YACzB,SAAS,CAAC,EAAE,MAAM,CAAC;SACnB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KACrB,CAAC;CACF;AAED,MAAM,WAAW,mBAAmB;IACnC,gEAAgE;IAChE,MAAM,EAAE,cAAc,CAAC;IACvB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,KAAK,EAAE,aAAa,CAAC;IACrB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAc9E;AAED,OAAO,EAAE,SAAS,IAAI,qBAAqB,EAAE,CAAC"}