@credal/actions 0.2.149 → 0.2.150

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.
@@ -14,9 +14,9 @@ export interface SlackSearchMessage {
14
14
  userName?: string;
15
15
  }>;
16
16
  members?: Array<{
17
- userId: string | undefined;
18
- userEmail: string | undefined;
19
- userName: string | undefined;
17
+ userId: string;
18
+ userEmail?: string;
19
+ userName?: string;
20
20
  }>;
21
21
  }
22
22
  declare const searchSlack: slackUserSearchSlackFunction;
@@ -11,7 +11,7 @@ import { WebClient } from "@slack/web-api";
11
11
  import { MISSING_AUTH_TOKEN } from "../../util/missingAuthConstants.js";
12
12
  import pLimit from "p-limit";
13
13
  /* ===================== Constants ===================== */
14
- const HIT_ENRICH_POOL = 5;
14
+ const HIT_ENRICH_POOL = 2; // keep concurrency conservative to avoid 429s
15
15
  const limitHit = pLimit(HIT_ENRICH_POOL);
16
16
  const MENTION_USER_RE = /<@([UW][A-Z0-9]+)(?:\|[^>]+)?>/g;
17
17
  const MENTION_CHANNEL_RE = /<#(C[A-Z0-9]+)(?:\|[^>]+)?>/g;
@@ -22,36 +22,39 @@ class SlackUserCache {
22
22
  constructor(client) {
23
23
  this.client = client;
24
24
  this.cache = new Map();
25
- this.pendingRequests = new Map();
26
- this.client = client;
25
+ this.pending = new Map();
26
+ }
27
+ getSync(id) {
28
+ return this.cache.get(id);
27
29
  }
28
30
  get(id) {
29
31
  return __awaiter(this, void 0, void 0, function* () {
30
32
  const cached = this.cache.get(id);
31
33
  if (cached)
32
34
  return cached;
33
- const pending = this.pendingRequests.get(id);
35
+ const pending = this.pending.get(id);
34
36
  if (pending)
35
37
  return pending;
38
+ // fallback to users.info only if we have to
36
39
  const promise = (() => __awaiter(this, void 0, void 0, function* () {
37
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
40
+ var _a, _b, _c, _d, _e;
38
41
  try {
39
42
  const res = yield this.client.users.info({ user: id });
40
- const u = {
41
- name: (_g = (_e = (_c = (_b = (_a = res.user) === null || _a === void 0 ? void 0 : _a.profile) === null || _b === void 0 ? void 0 : _b.display_name) !== null && _c !== void 0 ? _c : (_d = res.user) === null || _d === void 0 ? void 0 : _d.real_name) !== null && _e !== void 0 ? _e : (_f = res.user) === null || _f === void 0 ? void 0 : _f.name) !== null && _g !== void 0 ? _g : "",
42
- email: (_k = (_j = (_h = res.user) === null || _h === void 0 ? void 0 : _h.profile) === null || _j === void 0 ? void 0 : _j.email) !== null && _k !== void 0 ? _k : "",
43
- };
44
- if (res.user && id) {
43
+ if ((_a = res.user) === null || _a === void 0 ? void 0 : _a.id) {
44
+ const u = {
45
+ name: (_d = (_c = (_b = res.user.profile) === null || _b === void 0 ? void 0 : _b.display_name) !== null && _c !== void 0 ? _c : res.user.real_name) !== null && _d !== void 0 ? _d : res.user.name,
46
+ email: (_e = res.user.profile) === null || _e === void 0 ? void 0 : _e.email,
47
+ };
45
48
  this.cache.set(id, u);
46
49
  return u;
47
50
  }
48
51
  return undefined;
49
52
  }
50
53
  finally {
51
- this.pendingRequests.delete(id);
54
+ this.pending.delete(id);
52
55
  }
53
56
  }))();
54
- this.pendingRequests.set(id, promise);
57
+ this.pending.set(id, promise);
55
58
  return promise;
56
59
  });
57
60
  }
@@ -89,7 +92,7 @@ function lookupUserIdsByEmail(client, emails, cache) {
89
92
  return __awaiter(this, void 0, void 0, function* () {
90
93
  const ids = [];
91
94
  const settled = yield Promise.allSettled(emails.map((raw) => __awaiter(this, void 0, void 0, function* () {
92
- var _a, _b, _c, _d, _e, _f, _g;
95
+ var _a, _b, _c, _d, _e;
93
96
  const email = raw.trim();
94
97
  if (!email)
95
98
  return null;
@@ -97,8 +100,8 @@ function lookupUserIdsByEmail(client, emails, cache) {
97
100
  const id = (_a = res.user) === null || _a === void 0 ? void 0 : _a.id;
98
101
  if (id && res.user) {
99
102
  cache.set(id, {
100
- name: (_e = (_d = (_c = (_b = res.user.profile) === null || _b === void 0 ? void 0 : _b.display_name) !== null && _c !== void 0 ? _c : res.user.real_name) !== null && _d !== void 0 ? _d : res.user.name) !== null && _e !== void 0 ? _e : "",
101
- email: (_g = (_f = res.user.profile) === null || _f === void 0 ? void 0 : _f.email) !== null && _g !== void 0 ? _g : "",
103
+ name: (_d = (_c = (_b = res.user.profile) === null || _b === void 0 ? void 0 : _b.display_name) !== null && _c !== void 0 ? _c : res.user.real_name) !== null && _d !== void 0 ? _d : res.user.name,
104
+ email: (_e = res.user.profile) === null || _e === void 0 ? void 0 : _e.email,
102
105
  });
103
106
  }
104
107
  return id !== null && id !== void 0 ? id : null;
@@ -146,7 +149,7 @@ function fetchOneMessage(client, channel, ts) {
146
149
  function fetchThread(client, channel, threadTs) {
147
150
  return __awaiter(this, void 0, void 0, function* () {
148
151
  var _a;
149
- const r = yield client.conversations.replies({ channel, ts: threadTs, limit: 50 });
152
+ const r = yield client.conversations.replies({ channel, ts: threadTs, limit: 20 });
150
153
  return (_a = r.messages) !== null && _a !== void 0 ? _a : [];
151
154
  });
152
155
  }
@@ -157,10 +160,10 @@ function fetchContextWindow(client, channel, ts) {
157
160
  const anchor = yield fetchOneMessage(client, channel, ts);
158
161
  if (!anchor)
159
162
  return out;
160
- const before = yield client.conversations.history({ channel, latest: ts, inclusive: false, limit: 4 });
163
+ const before = yield client.conversations.history({ channel, latest: ts, inclusive: false, limit: 3 });
161
164
  out.push(...((_a = before.messages) !== null && _a !== void 0 ? _a : []).reverse());
162
165
  out.push(anchor);
163
- const after = yield client.conversations.history({ channel, oldest: ts, inclusive: false, limit: 5 });
166
+ const after = yield client.conversations.history({ channel, oldest: ts, inclusive: false, limit: 3 });
164
167
  out.push(...((_b = after.messages) !== null && _b !== void 0 ? _b : []));
165
168
  return out;
166
169
  });
@@ -170,26 +173,21 @@ function hasOverlap(messages, ids, minOverlap) {
170
173
  const overlap = ids.filter(id => participants.has(id)).length;
171
174
  return overlap >= minOverlap;
172
175
  }
173
- function expandSlackEntities(client, cache, raw) {
176
+ function expandSlackEntities(cache, raw) {
174
177
  return __awaiter(this, void 0, void 0, function* () {
175
178
  let text = raw;
176
- // resolve users
177
- const userIds = new Set();
178
- for (const m of raw.matchAll(MENTION_USER_RE))
179
- userIds.add(m[1]);
180
- const idToUser = {};
181
- yield Promise.all([...userIds].map((id) => __awaiter(this, void 0, void 0, function* () {
182
- const u = yield cache.get(id);
183
- idToUser[id] = { name: u === null || u === void 0 ? void 0 : u.name };
179
+ const userIds = [...raw.matchAll(MENTION_USER_RE)].map(m => m[1]);
180
+ // resolve all in parallel: prefer cache, else users.info
181
+ const idToName = {};
182
+ yield Promise.all(userIds.map((id) => __awaiter(this, void 0, void 0, function* () {
183
+ const u = yield cache.get(id); // get() will call users.info if missing
184
+ if (u === null || u === void 0 ? void 0 : u.name)
185
+ idToName[id] = u.name;
184
186
  })));
185
- text = text.replace(MENTION_USER_RE, (_, id) => { var _a, _b; return `@${(_b = (_a = idToUser[id]) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : id}`; });
186
- // channels
187
+ text = text.replace(MENTION_USER_RE, (_, id) => { var _a; return `@${(_a = idToName[id]) !== null && _a !== void 0 ? _a : id}`; });
187
188
  text = text.replace(MENTION_CHANNEL_RE, (_, id) => `#${id}`);
188
- // special mentions
189
189
  text = text.replace(SPECIAL_RE, (_, kind) => `@${kind}`);
190
- // subteams
191
190
  text = text.replace(SUBTEAM_RE, (_m, sid) => `@${sid}`);
192
- // links
193
191
  text = text.replace(/<([^>|]+)\|([^>]+)>/g, (_m, _url, label) => label);
194
192
  text = text.replace(/<([^>|]+)>/g, (_m, url) => url);
195
193
  return text;
@@ -247,96 +245,100 @@ const searchSlack = (_a) => __awaiter(void 0, [_a], void 0, function* ({ params,
247
245
  const { user_id: myUserId } = yield client.auth.test();
248
246
  if (!myUserId)
249
247
  throw new Error("Failed to get my user ID.");
250
- const me = myUserId ? yield cache.get(myUserId) : undefined;
248
+ // preload myself
249
+ const meInfo = yield cache.get(myUserId);
250
+ // resolve targets by email
251
251
  const targetIds = (emails === null || emails === void 0 ? void 0 : emails.length) ? yield lookupUserIdsByEmail(client, emails, cache) : [];
252
252
  const filteredTargetIds = targetIds.filter(id => id !== myUserId);
253
253
  const allMatches = [];
254
- // --- Parallel search execution for better performance ---
255
254
  const searchPromises = [];
256
- // Scoped DM/MPIM searches
257
255
  if (filteredTargetIds.length === 1) {
258
256
  searchPromises.push(searchScoped({ client, scope: `<@${filteredTargetIds[0]}>`, topic, timeRange, limit }));
259
257
  }
260
258
  else if (filteredTargetIds.length >= 2) {
261
- // Run MPIM lookup and individual DM searches in parallel
262
259
  const searchMPIM = () => __awaiter(void 0, void 0, void 0, function* () {
263
260
  const mpimName = yield tryGetMPIMName(client, filteredTargetIds);
264
261
  return mpimName ? searchScoped({ client, scope: mpimName, topic, timeRange, limit }) : [];
265
262
  });
266
263
  searchPromises.push(searchMPIM());
267
- // Add individual DM searches
268
264
  searchPromises.push(...filteredTargetIds.map(id => searchScoped({ client, scope: `<@${id}>`, topic, timeRange, limit })));
269
265
  }
270
266
  else if (channel) {
271
267
  searchPromises.push(searchScoped({ client, scope: normalizeChannelOperand(channel), topic, timeRange, limit }));
272
268
  }
273
- // Topic-wide search in parallel
274
- searchPromises.push(...(topic ? [searchByTopic({ client, topic, timeRange, limit })] : []));
275
- // Execute all searches in parallel
269
+ if (topic) {
270
+ searchPromises.push(searchByTopic({ client, topic, timeRange, limit }));
271
+ }
276
272
  const searchResults = yield Promise.all(searchPromises);
277
273
  searchResults.forEach(matches => allMatches.push(...matches));
278
- // --- Expand hits with context + filter overlap ---
279
- // Create a channel info cache to avoid redundant API calls
280
274
  const channelInfoCache = new Map();
281
275
  const expanded = yield Promise.all(allMatches.map(m => limitHit(() => __awaiter(void 0, void 0, void 0, function* () {
282
- var _a, _b, _c, _d, _e, _f, _g, _h;
276
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
283
277
  if (!m.ts || !((_a = m.channel) === null || _a === void 0 ? void 0 : _a.id))
284
278
  return null;
285
279
  const anchor = yield fetchOneMessage(client, m.channel.id, m.ts);
286
280
  const rootTs = (anchor === null || anchor === void 0 ? void 0 : anchor.thread_ts) || m.ts;
287
- let members = [];
288
- // Check convo type (DM, MPIM, channel) with caching
281
+ // channel info
289
282
  let channelInfo = channelInfoCache.get(m.channel.id);
290
283
  if (!channelInfo) {
291
284
  const convoInfo = yield client.conversations.info({ channel: m.channel.id });
292
- channelInfo = {
293
- isIm: (_c = (_b = convoInfo.channel) === null || _b === void 0 ? void 0 : _b.is_im) !== null && _c !== void 0 ? _c : false,
294
- isMpim: (_e = (_d = convoInfo.channel) === null || _d === void 0 ? void 0 : _d.is_mpim) !== null && _e !== void 0 ? _e : false,
295
- };
285
+ const isIm = (_c = (_b = convoInfo.channel) === null || _b === void 0 ? void 0 : _b.is_im) !== null && _c !== void 0 ? _c : false;
286
+ const isMpim = (_e = (_d = convoInfo.channel) === null || _d === void 0 ? void 0 : _d.is_mpim) !== null && _e !== void 0 ? _e : false;
287
+ let members = [];
288
+ if (isIm || isMpim) {
289
+ const res = yield client.conversations.members({ channel: m.channel.id });
290
+ members = (_f = res.members) !== null && _f !== void 0 ? _f : [];
291
+ }
292
+ channelInfo = { isIm, isMpim, members };
296
293
  channelInfoCache.set(m.channel.id, channelInfo);
297
294
  }
298
- const { isIm, isMpim } = channelInfo;
295
+ // context + permalink
299
296
  const [contextMsgs, permalink] = (anchor === null || anchor === void 0 ? void 0 : anchor.thread_ts)
300
- ? [yield fetchThread(client, m.channel.id, rootTs), yield getPermalink(client, m.channel.id, rootTs)]
301
- : [yield fetchContextWindow(client, m.channel.id, m.ts), yield getPermalink(client, m.channel.id, m.ts)];
297
+ ? [
298
+ yield fetchThread(client, m.channel.id, rootTs),
299
+ (_g = m.permalink) !== null && _g !== void 0 ? _g : (yield getPermalink(client, m.channel.id, rootTs)),
300
+ ]
301
+ : [
302
+ yield fetchContextWindow(client, m.channel.id, m.ts),
303
+ (_h = m.permalink) !== null && _h !== void 0 ? _h : (yield getPermalink(client, m.channel.id, m.ts)),
304
+ ];
305
+ // filter logic
302
306
  let passesFilter = false;
303
- if (isIm || isMpim) {
304
- // DM/MPIM: use members, not authorship
305
- const membersRes = (_f = (yield client.conversations.members({ channel: m.channel.id })).members) !== null && _f !== void 0 ? _f : [];
306
- members = yield Promise.all(membersRes.map((uid) => __awaiter(void 0, void 0, void 0, function* () {
307
- const u = yield cache.get(uid);
308
- return { userId: uid, userEmail: u === null || u === void 0 ? void 0 : u.email, userName: u === null || u === void 0 ? void 0 : u.name };
309
- })));
310
- const overlap = filteredTargetIds.filter(id => membersRes.includes(id)).length;
307
+ if (channelInfo.isIm || channelInfo.isMpim) {
308
+ const overlap = filteredTargetIds.filter(id => channelInfo.members.includes(id)).length;
311
309
  passesFilter = overlap >= 1;
312
310
  }
313
311
  else {
314
- // Channel: use authorship
315
312
  passesFilter = hasOverlap(contextMsgs, filteredTargetIds, 1);
316
313
  }
317
314
  if (filteredTargetIds.length && !passesFilter)
318
315
  return null;
319
316
  const context = yield Promise.all(contextMsgs.map((t) => __awaiter(void 0, void 0, void 0, function* () {
320
- var _a, _b;
321
- return ({
317
+ var _a;
318
+ const u = t.user ? yield cache.get(t.user) : undefined;
319
+ return {
322
320
  ts: t.ts,
323
- text: t.text ? yield expandSlackEntities(client, cache, t.text) : undefined,
324
- userEmail: t.user ? (_a = (yield cache.get(t.user))) === null || _a === void 0 ? void 0 : _a.email : undefined,
325
- userName: t.user ? (_b = (yield cache.get(t.user))) === null || _b === void 0 ? void 0 : _b.name : undefined,
326
- });
321
+ text: t.text ? yield expandSlackEntities(cache, t.text) : undefined,
322
+ userEmail: u === null || u === void 0 ? void 0 : u.email,
323
+ userName: (_a = u === null || u === void 0 ? void 0 : u.name) !== null && _a !== void 0 ? _a : t.username,
324
+ };
327
325
  })));
326
+ const anchorUser = (anchor === null || anchor === void 0 ? void 0 : anchor.user) ? yield cache.get(anchor.user) : undefined;
328
327
  return {
329
328
  channelId: m.channel.id,
330
329
  ts: rootTs,
331
- text: (anchor === null || anchor === void 0 ? void 0 : anchor.text) ? yield expandSlackEntities(client, cache, anchor.text) : undefined,
332
- userEmail: (anchor === null || anchor === void 0 ? void 0 : anchor.user) ? (_g = (yield cache.get(anchor.user))) === null || _g === void 0 ? void 0 : _g.email : undefined,
333
- userName: (anchor === null || anchor === void 0 ? void 0 : anchor.user) ? (_h = (yield cache.get(anchor.user))) === null || _h === void 0 ? void 0 : _h.name : undefined,
330
+ text: (anchor === null || anchor === void 0 ? void 0 : anchor.text) ? yield expandSlackEntities(cache, anchor.text) : undefined,
331
+ userEmail: anchorUser === null || anchorUser === void 0 ? void 0 : anchorUser.email,
332
+ userName: (_j = anchorUser === null || anchorUser === void 0 ? void 0 : anchorUser.name) !== null && _j !== void 0 ? _j : anchor === null || anchor === void 0 ? void 0 : anchor.username,
334
333
  context,
335
- permalink,
336
- members,
334
+ permalink: (_k = m.permalink) !== null && _k !== void 0 ? _k : permalink,
335
+ members: ((_l = channelInfo.members) !== null && _l !== void 0 ? _l : []).map(uid => {
336
+ const u = cache.getSync(uid);
337
+ return { userId: uid, userEmail: u === null || u === void 0 ? void 0 : u.email, userName: u === null || u === void 0 ? void 0 : u.name };
338
+ }),
337
339
  };
338
340
  }))));
339
- const results = dedupeAndSort(expanded.filter(h => h !== null));
341
+ const results = dedupeAndSort(expanded.filter(Boolean));
340
342
  return {
341
343
  query: topic !== null && topic !== void 0 ? topic : "",
342
344
  results: results.map(r => ({
@@ -344,7 +346,7 @@ const searchSlack = (_a) => __awaiter(void 0, [_a], void 0, function* ({ params,
344
346
  url: r.permalink || "",
345
347
  contents: r,
346
348
  })),
347
- currentUser: { userId: myUserId, userName: me === null || me === void 0 ? void 0 : me.name, userEmail: me === null || me === void 0 ? void 0 : me.email },
349
+ currentUser: { userId: myUserId, userName: meInfo === null || meInfo === void 0 ? void 0 : meInfo.name, userEmail: meInfo === null || meInfo === void 0 ? void 0 : meInfo.email },
348
350
  };
349
351
  });
350
352
  export default searchSlack;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@credal/actions",
3
- "version": "0.2.149",
3
+ "version": "0.2.150",
4
4
  "type": "module",
5
5
  "description": "AI Actions by Credal AI",
6
6
  "sideEffects": false,