@chat-adapter/slack 4.20.0 → 4.20.2

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.
package/dist/index.d.ts CHANGED
@@ -107,6 +107,18 @@ interface SlackThreadId {
107
107
  }
108
108
  /** Slack event payload (raw message format) */
109
109
  interface SlackEvent {
110
+ /** Rich text blocks containing structured elements (links, mentions, etc.) */
111
+ blocks?: Array<{
112
+ type: string;
113
+ elements?: Array<{
114
+ type: string;
115
+ elements?: Array<{
116
+ type: string;
117
+ url?: string;
118
+ text?: string;
119
+ }>;
120
+ }>;
121
+ }>;
110
122
  bot_id?: string;
111
123
  channel?: string;
112
124
  /** Channel type: "channel", "group", "mpim", or "im" (DM) */
@@ -162,6 +174,8 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
162
174
  private _botId;
163
175
  private readonly formatConverter;
164
176
  private static USER_CACHE_TTL_MS;
177
+ private static CHANNEL_CACHE_TTL_MS;
178
+ private static REVERSE_INDEX_TTL_MS;
165
179
  private readonly clientId;
166
180
  private readonly clientSecret;
167
181
  private readonly encryptionKey;
@@ -222,6 +236,11 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
222
236
  * Returns display name and real name, or falls back to user ID.
223
237
  */
224
238
  private lookupUser;
239
+ /**
240
+ * Look up channel name from Slack API with caching via state adapter.
241
+ * Returns channel name, or falls back to channel ID.
242
+ */
243
+ private lookupChannel;
225
244
  handleWebhook(request: Request, options?: WebhookOptions): Promise<Response>;
226
245
  /** Extract and dispatch events from a validated payload */
227
246
  private processEventPayload;
@@ -273,6 +292,7 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
273
292
  * Fires when a user (including the bot) joins a channel.
274
293
  */
275
294
  private handleMemberJoinedChannel;
295
+ private handleUserChange;
276
296
  /**
277
297
  * Publish a Home tab view for a user.
278
298
  * Slack API: views.publish
@@ -307,20 +327,41 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
307
327
  * detection doesn't apply.
308
328
  */
309
329
  private resolveInlineMentions;
330
+ /**
331
+ * Extract link URLs from a Slack event.
332
+ * Uses the `blocks` field (rich_text blocks with link elements) when available,
333
+ * falling back to parsing `<url>` patterns from the text field.
334
+ */
335
+ private extractLinks;
336
+ /**
337
+ * Create a LinkPreview for a URL. If the URL points to a Slack message,
338
+ * includes a `fetchMessage` callback that fetches and parses the linked message.
339
+ */
340
+ private createLinkPreview;
310
341
  private parseSlackMessage;
311
342
  /**
312
343
  * Create an Attachment object from a Slack file.
313
344
  * Includes a fetchData method that uses the bot token for auth.
314
345
  */
315
346
  private createAttachment;
347
+ /**
348
+ * Resolve @name mentions in text to Slack <@USER_ID> format using the
349
+ * reverse user cache. When multiple users share a display name, prefers
350
+ * the one who is a participant in the given thread.
351
+ */
352
+ private resolveOutgoingMentions;
353
+ /**
354
+ * Pre-process an outgoing message to resolve @name mentions before rendering.
355
+ */
356
+ private resolveMessageMentions;
316
357
  /**
317
358
  * Try to render a message using native Slack table blocks.
318
359
  * Returns blocks + fallback text if the message contains tables, null otherwise.
319
360
  */
320
361
  private renderWithTableBlocks;
321
- postMessage(threadId: string, message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
322
- postEphemeral(threadId: string, userId: string, message: AdapterPostableMessage): Promise<EphemeralMessage>;
323
- scheduleMessage(threadId: string, message: AdapterPostableMessage, options: {
362
+ postMessage(threadId: string, _message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
363
+ postEphemeral(threadId: string, userId: string, _message: AdapterPostableMessage): Promise<EphemeralMessage>;
364
+ scheduleMessage(threadId: string, _message: AdapterPostableMessage, options: {
324
365
  postAt: Date;
325
366
  }): Promise<ScheduledMessage>;
326
367
  openModal(triggerId: string, modal: ModalElement, contextId?: string): Promise<{
@@ -334,7 +375,7 @@ declare class SlackAdapter implements Adapter<SlackThreadId, unknown> {
334
375
  * Returns the file IDs of uploaded files.
335
376
  */
336
377
  private uploadFiles;
337
- editMessage(threadId: string, messageId: string, message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
378
+ editMessage(threadId: string, messageId: string, _message: AdapterPostableMessage): Promise<RawMessage<unknown>>;
338
379
  deleteMessage(threadId: string, messageId: string): Promise<void>;
339
380
  addReaction(threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
340
381
  removeReaction(threadId: string, messageId: string, emoji: EmojiValue | string): Promise<void>;
package/dist/index.js CHANGED
@@ -708,6 +708,19 @@ function radioSelectToBlock(radioSelect) {
708
708
 
709
709
  // src/index.ts
710
710
  var SLACK_USER_ID_PATTERN = /^[A-Z0-9_]+$/;
711
+ var SLACK_USER_ID_EXACT_PATTERN = /^U[A-Z0-9]+$/;
712
+ function findNextMention(text) {
713
+ const atIdx = text.indexOf("<@");
714
+ const hashIdx = text.indexOf("<#");
715
+ if (atIdx === -1) {
716
+ return hashIdx;
717
+ }
718
+ if (hashIdx === -1) {
719
+ return atIdx;
720
+ }
721
+ return Math.min(atIdx, hashIdx);
722
+ }
723
+ var SLACK_MESSAGE_URL_PATTERN = /^https?:\/\/[^/]+\.slack\.com\/archives\/([A-Z0-9]+)\/p(\d+)(?:\?.*)?$/;
711
724
  var SlackAdapter = class _SlackAdapter {
712
725
  name = "slack";
713
726
  userName;
@@ -720,8 +733,12 @@ var SlackAdapter = class _SlackAdapter {
720
733
  _botId = null;
721
734
  // Bot app ID (B_xxx) - different from user ID
722
735
  formatConverter = new SlackFormatConverter();
723
- static USER_CACHE_TTL_MS = 60 * 60 * 1e3;
724
- // 1 hour
736
+ static USER_CACHE_TTL_MS = 8 * 24 * 60 * 60 * 1e3;
737
+ // 8 days
738
+ static CHANNEL_CACHE_TTL_MS = 8 * 24 * 60 * 60 * 1e3;
739
+ // 8 days
740
+ static REVERSE_INDEX_TTL_MS = 8 * 24 * 60 * 60 * 1e3;
741
+ // 8 days
725
742
  // Multi-workspace support
726
743
  clientId;
727
744
  clientSecret;
@@ -992,6 +1009,15 @@ var SlackAdapter = class _SlackAdapter {
992
1009
  { displayName, realName },
993
1010
  _SlackAdapter.USER_CACHE_TTL_MS
994
1011
  );
1012
+ const normalizedName = displayName.toLowerCase();
1013
+ const reverseKey = `slack:user-by-name:${normalizedName}`;
1014
+ const existing = await this.chat.getState().getList(reverseKey);
1015
+ if (!existing.includes(userId)) {
1016
+ await this.chat.getState().appendToList(reverseKey, userId, {
1017
+ maxLength: 50,
1018
+ ttlMs: _SlackAdapter.REVERSE_INDEX_TTL_MS
1019
+ });
1020
+ }
995
1021
  }
996
1022
  this.logger.debug("Fetched user info", {
997
1023
  userId,
@@ -1004,6 +1030,37 @@ var SlackAdapter = class _SlackAdapter {
1004
1030
  return { displayName: userId, realName: userId };
1005
1031
  }
1006
1032
  }
1033
+ /**
1034
+ * Look up channel name from Slack API with caching via state adapter.
1035
+ * Returns channel name, or falls back to channel ID.
1036
+ */
1037
+ async lookupChannel(channelId) {
1038
+ const cacheKey = `slack:channel:${channelId}`;
1039
+ if (this.chat) {
1040
+ const cached = await this.chat.getState().get(cacheKey);
1041
+ if (cached) {
1042
+ return cached.name;
1043
+ }
1044
+ }
1045
+ try {
1046
+ const result = await this.client.conversations.info(
1047
+ this.withToken({ channel: channelId })
1048
+ );
1049
+ const name = result.channel?.name || channelId;
1050
+ if (this.chat) {
1051
+ await this.chat.getState().set(
1052
+ cacheKey,
1053
+ { name },
1054
+ _SlackAdapter.CHANNEL_CACHE_TTL_MS
1055
+ );
1056
+ }
1057
+ this.logger.debug("Fetched channel info", { channelId, name });
1058
+ return name;
1059
+ } catch (error) {
1060
+ this.logger.warn("Could not fetch channel info", { channelId, error });
1061
+ return channelId;
1062
+ }
1063
+ }
1007
1064
  async handleWebhook(request, options) {
1008
1065
  const body = await request.text();
1009
1066
  this.logger.debug("Slack webhook raw body", { body });
@@ -1099,6 +1156,8 @@ var SlackAdapter = class _SlackAdapter {
1099
1156
  event,
1100
1157
  options
1101
1158
  );
1159
+ } else if (event.type === "user_change") {
1160
+ this.handleUserChange(event);
1102
1161
  }
1103
1162
  }
1104
1163
  }
@@ -1605,6 +1664,19 @@ var SlackAdapter = class _SlackAdapter {
1605
1664
  options
1606
1665
  );
1607
1666
  }
1667
+ async handleUserChange(event) {
1668
+ if (!this.chat) {
1669
+ return;
1670
+ }
1671
+ try {
1672
+ await this.chat.getState().delete(`slack:user:${event.user.id}`);
1673
+ } catch (error) {
1674
+ this.logger.warn("Failed to invalidate user cache", {
1675
+ userId: event.user.id,
1676
+ error
1677
+ });
1678
+ }
1679
+ }
1608
1680
  /**
1609
1681
  * Publish a Home tab view for a user.
1610
1682
  * Slack API: views.publish
@@ -1668,41 +1740,56 @@ var SlackAdapter = class _SlackAdapter {
1668
1740
  */
1669
1741
  async resolveInlineMentions(text, skipSelfMention) {
1670
1742
  const userIds = /* @__PURE__ */ new Set();
1743
+ const channelIds = /* @__PURE__ */ new Set();
1671
1744
  for (const segment of text.split("<")) {
1672
1745
  const end = segment.indexOf(">");
1673
1746
  if (end === -1) {
1674
1747
  continue;
1675
1748
  }
1676
1749
  const inner = segment.slice(0, end);
1677
- if (!inner.startsWith("@")) {
1678
- continue;
1679
- }
1680
- const rest = inner.slice(1);
1681
- const pipeIdx = rest.indexOf("|");
1682
- const uid = pipeIdx >= 0 ? rest.slice(0, pipeIdx) : rest;
1683
- if (SLACK_USER_ID_PATTERN.test(uid)) {
1684
- userIds.add(uid);
1750
+ if (inner.startsWith("@")) {
1751
+ const rest = inner.slice(1);
1752
+ const pipeIdx = rest.indexOf("|");
1753
+ const uid = pipeIdx >= 0 ? rest.slice(0, pipeIdx) : rest;
1754
+ if (SLACK_USER_ID_PATTERN.test(uid)) {
1755
+ userIds.add(uid);
1756
+ }
1757
+ } else if (inner.startsWith("#")) {
1758
+ const rest = inner.slice(1);
1759
+ const pipeIdx = rest.indexOf("|");
1760
+ if (pipeIdx === -1 && SLACK_USER_ID_PATTERN.test(rest)) {
1761
+ channelIds.add(rest);
1762
+ }
1685
1763
  }
1686
1764
  }
1687
- if (userIds.size === 0) {
1765
+ if (userIds.size === 0 && channelIds.size === 0) {
1688
1766
  return text;
1689
1767
  }
1690
1768
  if (skipSelfMention && this._botUserId) {
1691
1769
  userIds.delete(this._botUserId);
1692
1770
  }
1693
- if (userIds.size === 0) {
1771
+ if (userIds.size === 0 && channelIds.size === 0) {
1694
1772
  return text;
1695
1773
  }
1696
- const lookups = await Promise.all(
1697
- [...userIds].map(async (uid) => {
1698
- const info = await this.lookupUser(uid);
1699
- return [uid, info.displayName];
1700
- })
1701
- );
1702
- const nameMap = new Map(lookups);
1774
+ const [userLookups, channelLookups] = await Promise.all([
1775
+ Promise.all(
1776
+ [...userIds].map(async (uid) => {
1777
+ const info = await this.lookupUser(uid);
1778
+ return [uid, info.displayName];
1779
+ })
1780
+ ),
1781
+ Promise.all(
1782
+ [...channelIds].map(async (cid) => {
1783
+ const name = await this.lookupChannel(cid);
1784
+ return [cid, name];
1785
+ })
1786
+ )
1787
+ ]);
1788
+ const userNameMap = new Map(userLookups);
1789
+ const channelNameMap = new Map(channelLookups);
1703
1790
  let result = "";
1704
1791
  let remaining = text;
1705
- let startIdx = remaining.indexOf("<@");
1792
+ let startIdx = findNextMention(remaining);
1706
1793
  while (startIdx !== -1) {
1707
1794
  result += remaining.slice(0, startIdx);
1708
1795
  remaining = remaining.slice(startIdx);
@@ -1710,20 +1797,89 @@ var SlackAdapter = class _SlackAdapter {
1710
1797
  if (endIdx === -1) {
1711
1798
  break;
1712
1799
  }
1800
+ const prefix = remaining[1];
1713
1801
  const inner = remaining.slice(2, endIdx);
1714
1802
  const pipeIdx = inner.indexOf("|");
1715
- const uid = pipeIdx >= 0 ? inner.slice(0, pipeIdx) : inner;
1716
- if (SLACK_USER_ID_PATTERN.test(uid)) {
1717
- const name = nameMap.get(uid);
1718
- result += name ? `<@${uid}|${name}>` : `<@${uid}>`;
1803
+ const id = pipeIdx >= 0 ? inner.slice(0, pipeIdx) : inner;
1804
+ if (prefix === "@" && SLACK_USER_ID_PATTERN.test(id)) {
1805
+ const name = userNameMap.get(id);
1806
+ result += name ? `<@${id}|${name}>` : `<@${id}>`;
1807
+ } else if (prefix === "#" && pipeIdx === -1 && channelNameMap.has(id)) {
1808
+ const name = channelNameMap.get(id);
1809
+ result += `<#${id}|${name}>`;
1719
1810
  } else {
1720
1811
  result += remaining.slice(0, endIdx + 1);
1721
1812
  }
1722
1813
  remaining = remaining.slice(endIdx + 1);
1723
- startIdx = remaining.indexOf("<@");
1814
+ startIdx = findNextMention(remaining);
1724
1815
  }
1725
1816
  return result + remaining;
1726
1817
  }
1818
+ /**
1819
+ * Extract link URLs from a Slack event.
1820
+ * Uses the `blocks` field (rich_text blocks with link elements) when available,
1821
+ * falling back to parsing `<url>` patterns from the text field.
1822
+ */
1823
+ extractLinks(event) {
1824
+ const urls = /* @__PURE__ */ new Set();
1825
+ if (event.blocks) {
1826
+ for (const block of event.blocks) {
1827
+ if (block.type === "rich_text" && block.elements) {
1828
+ for (const section of block.elements) {
1829
+ if (section.elements) {
1830
+ for (const element of section.elements) {
1831
+ if (element.type === "link" && element.url) {
1832
+ urls.add(element.url);
1833
+ }
1834
+ }
1835
+ }
1836
+ }
1837
+ }
1838
+ }
1839
+ }
1840
+ if (urls.size === 0 && event.text) {
1841
+ const urlPattern = /<(https?:\/\/[^>]+)>/g;
1842
+ for (const match of event.text.matchAll(urlPattern)) {
1843
+ const raw = match[1];
1844
+ const pipeIdx = raw.indexOf("|");
1845
+ urls.add(pipeIdx >= 0 ? raw.slice(0, pipeIdx) : raw);
1846
+ }
1847
+ }
1848
+ return [...urls].map((url) => this.createLinkPreview(url));
1849
+ }
1850
+ /**
1851
+ * Create a LinkPreview for a URL. If the URL points to a Slack message,
1852
+ * includes a `fetchMessage` callback that fetches and parses the linked message.
1853
+ */
1854
+ createLinkPreview(url) {
1855
+ const match = SLACK_MESSAGE_URL_PATTERN.exec(url);
1856
+ if (!match) {
1857
+ return { url };
1858
+ }
1859
+ const channel = match[1];
1860
+ const rawTs = match[2];
1861
+ const ts = `${rawTs.slice(0, rawTs.length - 6)}.${rawTs.slice(rawTs.length - 6)}`;
1862
+ const threadId = this.encodeThreadId({ channel, threadTs: ts });
1863
+ return {
1864
+ url,
1865
+ fetchMessage: async () => {
1866
+ const result = await this.client.conversations.history(
1867
+ this.withToken({
1868
+ channel,
1869
+ latest: ts,
1870
+ inclusive: true,
1871
+ limit: 1
1872
+ })
1873
+ );
1874
+ const messages = result.messages || [];
1875
+ const target = messages.find((msg) => msg.ts === ts);
1876
+ if (!target) {
1877
+ throw new Error(`Message not found: ${url}`);
1878
+ }
1879
+ return this.parseSlackMessage(target, threadId);
1880
+ }
1881
+ };
1882
+ }
1727
1883
  async parseSlackMessage(event, threadId, options) {
1728
1884
  const isMe = this.isMessageFromSelf(event);
1729
1885
  const skipSelfMention = options?.skipSelfMention ?? true;
@@ -1735,6 +1891,24 @@ var SlackAdapter = class _SlackAdapter {
1735
1891
  userName = userInfo.displayName;
1736
1892
  fullName = userInfo.realName;
1737
1893
  }
1894
+ if (event.user && this.chat) {
1895
+ try {
1896
+ const participantKey = `slack:thread-participants:${threadId}`;
1897
+ const participants = await this.chat.getState().getList(participantKey);
1898
+ if (!participants.includes(event.user)) {
1899
+ await this.chat.getState().appendToList(participantKey, event.user, {
1900
+ maxLength: 100,
1901
+ ttlMs: _SlackAdapter.REVERSE_INDEX_TTL_MS
1902
+ });
1903
+ }
1904
+ } catch (error) {
1905
+ this.logger.warn("Failed to track thread participant", {
1906
+ threadId,
1907
+ userId: event.user,
1908
+ error
1909
+ });
1910
+ }
1911
+ }
1738
1912
  const text = await this.resolveInlineMentions(rawText, skipSelfMention);
1739
1913
  return new Message({
1740
1914
  id: event.ts || "",
@@ -1756,7 +1930,8 @@ var SlackAdapter = class _SlackAdapter {
1756
1930
  },
1757
1931
  attachments: (event.files || []).map(
1758
1932
  (file) => this.createAttachment(file)
1759
- )
1933
+ ),
1934
+ links: this.extractLinks(event)
1760
1935
  });
1761
1936
  }
1762
1937
  /**
@@ -1794,11 +1969,119 @@ var SlackAdapter = class _SlackAdapter {
1794
1969
  `Failed to fetch file: ${response.status} ${response.statusText}`
1795
1970
  );
1796
1971
  }
1972
+ const contentType = response.headers.get("content-type") ?? "";
1973
+ if (contentType.includes("text/html")) {
1974
+ throw new NetworkError(
1975
+ "slack",
1976
+ `Failed to download file from Slack: received HTML login page instead of file data. Ensure your Slack app has the "files:read" OAuth scope. URL: ${url}`
1977
+ );
1978
+ }
1797
1979
  const arrayBuffer = await response.arrayBuffer();
1798
1980
  return Buffer.from(arrayBuffer);
1799
1981
  } : void 0
1800
1982
  };
1801
1983
  }
1984
+ /**
1985
+ * Resolve @name mentions in text to Slack <@USER_ID> format using the
1986
+ * reverse user cache. When multiple users share a display name, prefers
1987
+ * the one who is a participant in the given thread.
1988
+ */
1989
+ async resolveOutgoingMentions(text, threadId) {
1990
+ if (!this.chat) {
1991
+ return text;
1992
+ }
1993
+ const state = this.chat.getState();
1994
+ const mentionPattern = /@(\w+)/g;
1995
+ const mentions = /* @__PURE__ */ new Map();
1996
+ for (const match of text.matchAll(mentionPattern)) {
1997
+ const name = match[1];
1998
+ if (SLACK_USER_ID_EXACT_PATTERN.test(name)) {
1999
+ continue;
2000
+ }
2001
+ const idx = match.index;
2002
+ if (idx > 0 && text[idx - 1] === "<") {
2003
+ continue;
2004
+ }
2005
+ if (!mentions.has(name.toLowerCase())) {
2006
+ mentions.set(name.toLowerCase(), []);
2007
+ }
2008
+ }
2009
+ if (mentions.size === 0) {
2010
+ return text;
2011
+ }
2012
+ for (const name of mentions.keys()) {
2013
+ const userIds = await state.getList(`slack:user-by-name:${name}`);
2014
+ const unique = [...new Set(userIds)];
2015
+ mentions.set(name, unique);
2016
+ }
2017
+ let participants = null;
2018
+ const needsParticipants = [...mentions.values()].some(
2019
+ (ids) => ids.length > 1
2020
+ );
2021
+ if (needsParticipants) {
2022
+ const participantList = await state.getList(
2023
+ `slack:thread-participants:${threadId}`
2024
+ );
2025
+ participants = new Set(participantList);
2026
+ }
2027
+ return text.replace(
2028
+ mentionPattern,
2029
+ (match, name, offset) => {
2030
+ if (offset > 0 && text[offset - 1] === "<") {
2031
+ return match;
2032
+ }
2033
+ if (SLACK_USER_ID_EXACT_PATTERN.test(name)) {
2034
+ return match;
2035
+ }
2036
+ const userIds = mentions.get(name.toLowerCase());
2037
+ if (!userIds || userIds.length === 0) {
2038
+ return match;
2039
+ }
2040
+ if (userIds.length === 1) {
2041
+ return `<@${userIds[0]}>`;
2042
+ }
2043
+ if (participants) {
2044
+ const inThread = userIds.filter((id) => participants.has(id));
2045
+ if (inThread.length === 1) {
2046
+ return `<@${inThread[0]}>`;
2047
+ }
2048
+ }
2049
+ return match;
2050
+ }
2051
+ );
2052
+ }
2053
+ /**
2054
+ * Pre-process an outgoing message to resolve @name mentions before rendering.
2055
+ */
2056
+ async resolveMessageMentions(message, threadId) {
2057
+ if (!this.chat) {
2058
+ return message;
2059
+ }
2060
+ if (typeof message === "string") {
2061
+ return this.resolveOutgoingMentions(message, threadId);
2062
+ }
2063
+ if (typeof message === "object" && message !== null) {
2064
+ if ("raw" in message && typeof message.raw === "string") {
2065
+ return {
2066
+ ...message,
2067
+ raw: await this.resolveOutgoingMentions(
2068
+ message.raw,
2069
+ threadId
2070
+ )
2071
+ };
2072
+ }
2073
+ if ("markdown" in message && typeof message.markdown === "string") {
2074
+ return {
2075
+ ...message,
2076
+ markdown: await this.resolveOutgoingMentions(
2077
+ message.markdown,
2078
+ threadId
2079
+ )
2080
+ };
2081
+ }
2082
+ }
2083
+ return message;
2084
+ }
1802
2085
  /**
1803
2086
  * Try to render a message using native Slack table blocks.
1804
2087
  * Returns blocks + fallback text if the message contains tables, null otherwise.
@@ -1825,7 +2108,8 @@ var SlackAdapter = class _SlackAdapter {
1825
2108
  );
1826
2109
  return { text: fallbackText, blocks };
1827
2110
  }
1828
- async postMessage(threadId, message) {
2111
+ async postMessage(threadId, _message) {
2112
+ const message = await this.resolveMessageMentions(_message, threadId);
1829
2113
  const { channel, threadTs } = this.decodeThreadId(threadId);
1830
2114
  try {
1831
2115
  const files = extractFiles(message);
@@ -1929,7 +2213,8 @@ var SlackAdapter = class _SlackAdapter {
1929
2213
  this.handleSlackError(error);
1930
2214
  }
1931
2215
  }
1932
- async postEphemeral(threadId, userId, message) {
2216
+ async postEphemeral(threadId, userId, _message) {
2217
+ const message = await this.resolveMessageMentions(_message, threadId);
1933
2218
  const { channel, threadTs } = this.decodeThreadId(threadId);
1934
2219
  try {
1935
2220
  const card = extractCard(message);
@@ -2022,7 +2307,8 @@ var SlackAdapter = class _SlackAdapter {
2022
2307
  this.handleSlackError(error);
2023
2308
  }
2024
2309
  }
2025
- async scheduleMessage(threadId, message, options) {
2310
+ async scheduleMessage(threadId, _message, options) {
2311
+ const message = await this.resolveMessageMentions(_message, threadId);
2026
2312
  const { channel, threadTs } = this.decodeThreadId(threadId);
2027
2313
  const postAtUnix = Math.floor(options.postAt.getTime() / 1e3);
2028
2314
  if (postAtUnix <= Math.floor(Date.now() / 1e3)) {
@@ -2208,7 +2494,8 @@ var SlackAdapter = class _SlackAdapter {
2208
2494
  }
2209
2495
  return fileIds;
2210
2496
  }
2211
- async editMessage(threadId, messageId, message) {
2497
+ async editMessage(threadId, messageId, _message) {
2498
+ const message = await this.resolveMessageMentions(_message, threadId);
2212
2499
  const ephemeral = this.decodeEphemeralMessageId(messageId);
2213
2500
  if (ephemeral) {
2214
2501
  const { threadTs } = this.decodeThreadId(threadId);
@@ -2769,7 +3056,8 @@ var SlackAdapter = class _SlackAdapter {
2769
3056
  },
2770
3057
  attachments: (event.files || []).map(
2771
3058
  (file) => this.createAttachment(file)
2772
- )
3059
+ ),
3060
+ links: this.extractLinks(event)
2773
3061
  });
2774
3062
  }
2775
3063
  // =========================================================================