@cuylabs/channel-slack 0.1.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 (47) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +168 -0
  3. package/dist/activity-ByrD9Ftr.d.ts +66 -0
  4. package/dist/assistant.d.ts +58 -0
  5. package/dist/assistant.js +188 -0
  6. package/dist/bolt.d.ts +344 -0
  7. package/dist/bolt.js +705 -0
  8. package/dist/chunk-BODPT4I6.js +322 -0
  9. package/dist/chunk-FPCE5V5Y.js +292 -0
  10. package/dist/chunk-FX2JOVX5.js +405 -0
  11. package/dist/chunk-JZG4IETE.js +141 -0
  12. package/dist/chunk-NE57BLLU.js +0 -0
  13. package/dist/chunk-TWJGVDA2.js +108 -0
  14. package/dist/core.d.ts +425 -0
  15. package/dist/core.js +42 -0
  16. package/dist/diagnostics.d.ts +105 -0
  17. package/dist/diagnostics.js +8 -0
  18. package/dist/feedback.d.ts +137 -0
  19. package/dist/feedback.js +128 -0
  20. package/dist/history.d.ts +266 -0
  21. package/dist/history.js +747 -0
  22. package/dist/index.d.ts +4 -0
  23. package/dist/index.js +57 -0
  24. package/dist/logging-Bl3HfcC8.d.ts +8 -0
  25. package/dist/policy.d.ts +130 -0
  26. package/dist/policy.js +16 -0
  27. package/dist/setup.d.ts +165 -0
  28. package/dist/setup.js +453 -0
  29. package/dist/shared.d.ts +2 -0
  30. package/dist/shared.js +43 -0
  31. package/dist/targets.d.ts +113 -0
  32. package/dist/targets.js +484 -0
  33. package/dist/users.d.ts +109 -0
  34. package/dist/users.js +240 -0
  35. package/docs/concepts/activity.md +33 -0
  36. package/docs/concepts/bolt-runtime.md +30 -0
  37. package/docs/concepts/message-policy.md +49 -0
  38. package/docs/concepts/setup-requirements.md +44 -0
  39. package/docs/concepts/supplemental-history.md +55 -0
  40. package/docs/recipes/app-mention-handler.md +34 -0
  41. package/docs/recipes/assistant-thread-handler.md +28 -0
  42. package/docs/recipes/generate-slack-manifest.md +28 -0
  43. package/docs/recipes/history-visibility.md +36 -0
  44. package/docs/recipes/socket-mode-app.md +29 -0
  45. package/docs/reference/channel-slack-boundary.md +50 -0
  46. package/docs/reference/exports.md +32 -0
  47. package/package.json +130 -0
@@ -0,0 +1,484 @@
1
+ // src/targets/parse.ts
2
+ var CHANNEL_PREFIXES = /* @__PURE__ */ new Set(["channel", "conversation", "room"]);
3
+ var USER_PREFIXES = /* @__PURE__ */ new Set(["user", "member"]);
4
+ var CHANNEL_ID_PATTERN = /^[CDG](?=[A-Z0-9]*\d)[A-Z0-9]{2,}$/i;
5
+ var USER_ID_PATTERN = /^[UW](?=[A-Z0-9]*\d)[A-Z0-9]{2,}$/i;
6
+ var CHANNEL_MENTION_PATTERN = /^<#([CDG][A-Z0-9]+)(?:\|([^>]+))?>$/i;
7
+ var USER_MENTION_PATTERN = /^<@([UW][A-Z0-9]+)(?:\|([^>]+))?>$/i;
8
+ var CHANNEL_ARCHIVE_URL_PATTERN = /\/archives\/([CDG][A-Z0-9]+)/i;
9
+ function parseSlackTarget(input, options = {}) {
10
+ const normalizedInput = input.trim();
11
+ if (!normalizedInput) {
12
+ return void 0;
13
+ }
14
+ const channelMention = CHANNEL_MENTION_PATTERN.exec(normalizedInput);
15
+ if (channelMention) {
16
+ return idTarget({
17
+ input: normalizedInput,
18
+ kind: "channel",
19
+ id: channelMention[1],
20
+ label: channelMention[2],
21
+ source: "mention"
22
+ });
23
+ }
24
+ const userMention = USER_MENTION_PATTERN.exec(normalizedInput);
25
+ if (userMention) {
26
+ return idTarget({
27
+ input: normalizedInput,
28
+ kind: "user",
29
+ id: userMention[1],
30
+ label: userMention[2],
31
+ source: "mention"
32
+ });
33
+ }
34
+ const archiveUrl = CHANNEL_ARCHIVE_URL_PATTERN.exec(normalizedInput);
35
+ if (archiveUrl) {
36
+ return idTarget({
37
+ input: normalizedInput,
38
+ kind: "channel",
39
+ id: archiveUrl[1],
40
+ source: "link"
41
+ });
42
+ }
43
+ const prefixed = parsePrefixedTarget(normalizedInput);
44
+ if (prefixed) {
45
+ return prefixed;
46
+ }
47
+ if (normalizedInput.startsWith("#")) {
48
+ return parseDecoratedTarget(normalizedInput, "channel");
49
+ }
50
+ if (normalizedInput.startsWith("@")) {
51
+ return parseDecoratedTarget(normalizedInput, "user");
52
+ }
53
+ if (isChannelId(normalizedInput)) {
54
+ return idTarget({
55
+ input: normalizedInput,
56
+ kind: "channel",
57
+ id: normalizedInput,
58
+ source: "id"
59
+ });
60
+ }
61
+ if (isUserId(normalizedInput)) {
62
+ return idTarget({
63
+ input: normalizedInput,
64
+ kind: "user",
65
+ id: normalizedInput,
66
+ source: "id"
67
+ });
68
+ }
69
+ const allowBareNames = options.allowBareNames ?? Boolean(options.defaultKind);
70
+ if (allowBareNames && options.defaultKind) {
71
+ return nameTarget({
72
+ input: normalizedInput,
73
+ kind: options.defaultKind,
74
+ name: normalizedInput,
75
+ source: "name"
76
+ });
77
+ }
78
+ return void 0;
79
+ }
80
+ function parseSlackTargets(inputs, options = {}) {
81
+ const values = Array.isArray(inputs) ? inputs : [inputs];
82
+ const targets = [];
83
+ const invalid = [];
84
+ for (const input of values) {
85
+ const target = parseSlackTarget(input, options);
86
+ if (target) {
87
+ targets.push(target);
88
+ } else {
89
+ const trimmed = input.trim();
90
+ if (trimmed) {
91
+ invalid.push(trimmed);
92
+ }
93
+ }
94
+ }
95
+ return { targets, invalid };
96
+ }
97
+ function parsePrefixedTarget(input) {
98
+ const separator = input.indexOf(":");
99
+ if (separator <= 0) {
100
+ return void 0;
101
+ }
102
+ const prefix = input.slice(0, separator).trim().toLowerCase();
103
+ const value = input.slice(separator + 1).trim();
104
+ if (!value) {
105
+ return void 0;
106
+ }
107
+ if (CHANNEL_PREFIXES.has(prefix)) {
108
+ return valueTarget(input, "channel", stripLeading(value, "#"), "prefixed");
109
+ }
110
+ if (USER_PREFIXES.has(prefix)) {
111
+ return valueTarget(input, "user", stripLeading(value, "@"), "prefixed");
112
+ }
113
+ return void 0;
114
+ }
115
+ function parseDecoratedTarget(input, kind) {
116
+ const value = input.slice(1).trim();
117
+ if (!value) {
118
+ return void 0;
119
+ }
120
+ return valueTarget(input, kind, value, "name");
121
+ }
122
+ function valueTarget(input, kind, value, source) {
123
+ if (kind === "channel" && isChannelId(value)) {
124
+ return idTarget({ input, kind, id: value, source });
125
+ }
126
+ if (kind === "user" && isUserId(value)) {
127
+ return idTarget({ input, kind, id: value, source });
128
+ }
129
+ return nameTarget({ input, kind, name: value, source });
130
+ }
131
+ function idTarget(params) {
132
+ const id = normalizeSlackId(params.id);
133
+ return {
134
+ input: params.input,
135
+ kind: params.kind,
136
+ valueKind: "id",
137
+ value: id,
138
+ source: params.source,
139
+ id,
140
+ ...params.label ? { label: params.label } : {}
141
+ };
142
+ }
143
+ function nameTarget(params) {
144
+ const name = normalizeTargetName(params.name);
145
+ return {
146
+ input: params.input,
147
+ kind: params.kind,
148
+ valueKind: "name",
149
+ value: name,
150
+ source: params.source,
151
+ name
152
+ };
153
+ }
154
+ function stripLeading(value, leading) {
155
+ return value.startsWith(leading) ? value.slice(1).trim() : value;
156
+ }
157
+ function normalizeSlackId(value) {
158
+ return value.trim().toUpperCase();
159
+ }
160
+ function normalizeTargetName(value) {
161
+ return value.trim().replace(/^[@#]+/, "").trim();
162
+ }
163
+ function isChannelId(value) {
164
+ return CHANNEL_ID_PATTERN.test(value.trim());
165
+ }
166
+ function isUserId(value) {
167
+ return USER_ID_PATTERN.test(value.trim());
168
+ }
169
+
170
+ // src/targets/resolve.ts
171
+ var DEFAULT_LIMIT = 200;
172
+ async function resolveSlackChannelTargets(options) {
173
+ const result = collectDirectTargets(options.targets, "channel");
174
+ if (result.pending.length === 0) {
175
+ return {
176
+ resolved: result.resolved,
177
+ unresolved: result.unresolved
178
+ };
179
+ }
180
+ if (!options.client.conversations?.list) {
181
+ return {
182
+ resolved: result.resolved,
183
+ unresolved: [
184
+ ...result.unresolved,
185
+ ...result.pending.map(
186
+ (pending) => resolutionError(
187
+ pending.target,
188
+ "missing-client-method",
189
+ "Slack client does not expose conversations.list."
190
+ )
191
+ )
192
+ ]
193
+ };
194
+ }
195
+ const pendingByName = toPendingMap(result.pending);
196
+ try {
197
+ let cursor;
198
+ do {
199
+ const response = await options.client.conversations.list({
200
+ limit: options.limit ?? DEFAULT_LIMIT,
201
+ types: options.types ?? "public_channel,private_channel",
202
+ exclude_archived: options.includeArchived === true ? false : true,
203
+ ...cursor ? { cursor } : {},
204
+ ...options.token ? { token: options.token } : {}
205
+ });
206
+ assertSlackListOk(response, "conversations.list");
207
+ for (const channel of readArray(readRecord(response)?.channels)) {
208
+ const record = readRecord(channel);
209
+ if (!record) continue;
210
+ const names = channelLookupNames(record);
211
+ for (const name of names) {
212
+ const matches = pendingByName.get(name);
213
+ if (!matches) continue;
214
+ for (const pending of matches) {
215
+ result.resolved.push(resolvedFromRecord(pending.target, record));
216
+ }
217
+ pendingByName.delete(name);
218
+ }
219
+ if (pendingByName.size === 0) {
220
+ break;
221
+ }
222
+ }
223
+ cursor = readNextCursor(response);
224
+ } while (cursor && pendingByName.size > 0);
225
+ return finalizeResolution(result, pendingByName);
226
+ } catch (error) {
227
+ return {
228
+ resolved: result.resolved,
229
+ unresolved: [
230
+ ...result.unresolved,
231
+ ...result.pending.map(
232
+ (pending) => resolutionError(
233
+ pending.target,
234
+ "api-error",
235
+ "Slack channel target resolution failed.",
236
+ error
237
+ )
238
+ )
239
+ ]
240
+ };
241
+ }
242
+ }
243
+ async function resolveSlackUserTargets(options) {
244
+ const result = collectDirectTargets(options.targets, "user");
245
+ if (result.pending.length === 0) {
246
+ return {
247
+ resolved: result.resolved,
248
+ unresolved: result.unresolved
249
+ };
250
+ }
251
+ if (!options.client.users?.list) {
252
+ return {
253
+ resolved: result.resolved,
254
+ unresolved: [
255
+ ...result.unresolved,
256
+ ...result.pending.map(
257
+ (pending) => resolutionError(
258
+ pending.target,
259
+ "missing-client-method",
260
+ "Slack client does not expose users.list."
261
+ )
262
+ )
263
+ ]
264
+ };
265
+ }
266
+ const pendingByName = toPendingMap(result.pending);
267
+ try {
268
+ let cursor;
269
+ do {
270
+ const response = await options.client.users.list({
271
+ limit: options.limit ?? DEFAULT_LIMIT,
272
+ ...cursor ? { cursor } : {},
273
+ ...options.token ? { token: options.token } : {}
274
+ });
275
+ assertSlackListOk(response, "users.list");
276
+ for (const member of readArray(readRecord(response)?.members)) {
277
+ const record = readRecord(member);
278
+ if (!record || shouldSkipUser(record, options)) continue;
279
+ const names = userLookupNames(record);
280
+ for (const name of names) {
281
+ const matches = pendingByName.get(name);
282
+ if (!matches) continue;
283
+ for (const pending of matches) {
284
+ result.resolved.push(resolvedFromRecord(pending.target, record));
285
+ }
286
+ pendingByName.delete(name);
287
+ }
288
+ if (pendingByName.size === 0) {
289
+ break;
290
+ }
291
+ }
292
+ cursor = readNextCursor(response);
293
+ } while (cursor && pendingByName.size > 0);
294
+ return finalizeResolution(result, pendingByName);
295
+ } catch (error) {
296
+ return {
297
+ resolved: result.resolved,
298
+ unresolved: [
299
+ ...result.unresolved,
300
+ ...result.pending.map(
301
+ (pending) => resolutionError(
302
+ pending.target,
303
+ "api-error",
304
+ "Slack user target resolution failed.",
305
+ error
306
+ )
307
+ )
308
+ ]
309
+ };
310
+ }
311
+ }
312
+ async function resolveSlackAllowedChannelIds(options) {
313
+ const result = await resolveSlackChannelTargets({
314
+ ...options,
315
+ client: options.client ?? {}
316
+ });
317
+ return {
318
+ ok: result.unresolved.length === 0,
319
+ allowedChannelIds: uniqueIds(result.resolved.map((target) => target.id)),
320
+ resolved: result.resolved,
321
+ unresolved: result.unresolved
322
+ };
323
+ }
324
+ function collectDirectTargets(inputs, expectedKind) {
325
+ const resolved = [];
326
+ const unresolved = [];
327
+ const pending = [];
328
+ for (const input of inputs) {
329
+ const target = typeof input === "string" ? parseSlackTarget(input, { defaultKind: expectedKind }) : input;
330
+ const originalInput = typeof input === "string" ? input.trim() : input.input;
331
+ if (!target) {
332
+ if (originalInput) {
333
+ unresolved.push({
334
+ input: originalInput,
335
+ code: "invalid-target",
336
+ message: `Could not parse Slack ${expectedKind} target: ${originalInput}`
337
+ });
338
+ }
339
+ continue;
340
+ }
341
+ if (target.kind !== expectedKind) {
342
+ unresolved.push({
343
+ input: target.input,
344
+ kind: target.kind,
345
+ code: "target-kind-mismatch",
346
+ message: `Expected Slack ${expectedKind} target but received ${target.kind} target.`
347
+ });
348
+ continue;
349
+ }
350
+ if (target.id) {
351
+ resolved.push({
352
+ input: target.input,
353
+ kind: target.kind,
354
+ id: target.id,
355
+ valueKind: target.valueKind,
356
+ ...target.name ? { name: target.name } : {},
357
+ ...target.label ? { label: target.label } : {}
358
+ });
359
+ continue;
360
+ }
361
+ if (target.name) {
362
+ pending.push({
363
+ target,
364
+ lookupName: normalizeLookupName(target.name)
365
+ });
366
+ }
367
+ }
368
+ return { resolved, unresolved, pending };
369
+ }
370
+ function finalizeResolution(result, pendingByName) {
371
+ const unresolved = [...result.unresolved];
372
+ for (const pending of pendingByName.values()) {
373
+ for (const entry of pending) {
374
+ unresolved.push(
375
+ resolutionError(
376
+ entry.target,
377
+ "not-found",
378
+ `Slack ${entry.target.kind} target was not found: ${entry.target.name}`
379
+ )
380
+ );
381
+ }
382
+ }
383
+ return {
384
+ resolved: result.resolved,
385
+ unresolved
386
+ };
387
+ }
388
+ function toPendingMap(pending) {
389
+ const map = /* @__PURE__ */ new Map();
390
+ for (const entry of pending) {
391
+ const existing = map.get(entry.lookupName);
392
+ if (existing) {
393
+ existing.push(entry);
394
+ } else {
395
+ map.set(entry.lookupName, [entry]);
396
+ }
397
+ }
398
+ return map;
399
+ }
400
+ function resolvedFromRecord(target, record) {
401
+ const id = readString(record.id) ?? target.id ?? target.value;
402
+ const name = target.name ?? readString(record.name) ?? readString(readRecord(record.profile)?.display_name) ?? readString(record.real_name);
403
+ return {
404
+ input: target.input,
405
+ kind: target.kind,
406
+ id,
407
+ valueKind: target.valueKind,
408
+ ...name ? { name } : {},
409
+ ...target.label ? { label: target.label } : {},
410
+ raw: record
411
+ };
412
+ }
413
+ function resolutionError(target, code, message, error) {
414
+ return {
415
+ input: target.input,
416
+ code,
417
+ message,
418
+ kind: target.kind,
419
+ ...target.name ? { name: target.name } : {},
420
+ ...error ? { error } : {}
421
+ };
422
+ }
423
+ function channelLookupNames(record) {
424
+ return uniqueLookupNames([
425
+ readString(record.name),
426
+ readString(record.name_normalized)
427
+ ]);
428
+ }
429
+ function userLookupNames(record) {
430
+ const profile = readRecord(record.profile);
431
+ return uniqueLookupNames([
432
+ readString(record.name),
433
+ readString(record.real_name),
434
+ readString(profile?.display_name),
435
+ readString(profile?.display_name_normalized),
436
+ readString(profile?.real_name),
437
+ readString(profile?.real_name_normalized)
438
+ ]);
439
+ }
440
+ function uniqueLookupNames(values) {
441
+ return [...new Set(values.map(normalizeLookupName).filter(Boolean))];
442
+ }
443
+ function uniqueIds(values) {
444
+ return [...new Set(values.map((value) => value.trim()).filter(Boolean))];
445
+ }
446
+ function normalizeLookupName(value) {
447
+ return value?.trim().replace(/^[@#]+/, "").toLowerCase() ?? "";
448
+ }
449
+ function shouldSkipUser(record, options) {
450
+ if (options.includeDeleted !== true && record.deleted === true) {
451
+ return true;
452
+ }
453
+ if (options.includeBots !== true && record.is_bot === true) {
454
+ return true;
455
+ }
456
+ return false;
457
+ }
458
+ function assertSlackListOk(response, method) {
459
+ const record = readRecord(response);
460
+ if (record?.ok === false) {
461
+ const code = readString(record.error);
462
+ throw new Error(`${method} failed${code ? `: ${code}` : ""}.`);
463
+ }
464
+ }
465
+ function readNextCursor(response) {
466
+ const metadata = readRecord(readRecord(response)?.response_metadata);
467
+ return readString(metadata?.next_cursor);
468
+ }
469
+ function readArray(value) {
470
+ return Array.isArray(value) ? value : [];
471
+ }
472
+ function readRecord(value) {
473
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
474
+ }
475
+ function readString(value) {
476
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
477
+ }
478
+ export {
479
+ parseSlackTarget,
480
+ parseSlackTargets,
481
+ resolveSlackAllowedChannelIds,
482
+ resolveSlackChannelTargets,
483
+ resolveSlackUserTargets
484
+ };
@@ -0,0 +1,109 @@
1
+ interface SlackUserProfile {
2
+ userId: string;
3
+ displayName?: string;
4
+ realName?: string;
5
+ email?: string;
6
+ }
7
+ interface SlackUsersInfoClient {
8
+ users: {
9
+ info(args: {
10
+ user: string;
11
+ token?: string;
12
+ }): Promise<unknown>;
13
+ };
14
+ }
15
+ interface SlackUserProfileResolverInput {
16
+ userId: string;
17
+ teamId?: string;
18
+ botToken?: string;
19
+ }
20
+ interface SlackUserProfileLookupErrorContext extends SlackUserProfileResolverInput {
21
+ error: unknown;
22
+ }
23
+ interface CreateSlackUserProfileResolverOptions {
24
+ botToken?: string;
25
+ client?: SlackUsersInfoClient;
26
+ clientFactory?: (botToken: string) => SlackUsersInfoClient;
27
+ /**
28
+ * Maximum cached profile entries.
29
+ *
30
+ * @default 5000
31
+ */
32
+ maxEntries?: number;
33
+ /**
34
+ * Successful lookup cache TTL.
35
+ *
36
+ * @default 1800000
37
+ */
38
+ ttlMs?: number;
39
+ /**
40
+ * Failed lookup fallback cache TTL.
41
+ *
42
+ * @default min(ttlMs, 300000)
43
+ */
44
+ errorTtlMs?: number;
45
+ onLookupError?: (context: SlackUserProfileLookupErrorContext) => void;
46
+ }
47
+ interface SlackUserProfileResolver {
48
+ resolve(input: SlackUserProfileResolverInput): Promise<SlackUserProfile>;
49
+ clear(): void;
50
+ }
51
+ declare function createSlackUserProfileResolver(options?: CreateSlackUserProfileResolverOptions): SlackUserProfileResolver;
52
+
53
+ interface SlackUserMentionEnrichmentInput {
54
+ text: string;
55
+ teamId?: string;
56
+ botToken?: string;
57
+ }
58
+ interface SlackUserMentionLabelContext {
59
+ userId: string;
60
+ mention: string;
61
+ profile: SlackUserProfile;
62
+ }
63
+ type SlackUserMentionLabelFormatter = (context: SlackUserMentionLabelContext) => string | undefined;
64
+ interface SlackUserMentionEnrichmentErrorContext {
65
+ userId: string;
66
+ teamId?: string;
67
+ error: unknown;
68
+ }
69
+ interface EnrichSlackUserMentionsOptions extends CreateSlackUserProfileResolverOptions {
70
+ /**
71
+ * Reusable profile resolver. Prefer passing one created by
72
+ * `createSlackUserProfileResolver(...)` so profile cache entries are shared
73
+ * across messages.
74
+ */
75
+ profileResolver?: Pick<SlackUserProfileResolver, "resolve">;
76
+ /**
77
+ * Maximum distinct user mention IDs to look up for one input message.
78
+ *
79
+ * @default 20
80
+ */
81
+ maxLookups?: number;
82
+ /**
83
+ * Maximum concurrent profile lookups for one input message.
84
+ *
85
+ * @default 4
86
+ */
87
+ concurrency?: number;
88
+ /**
89
+ * Format the model-visible mention label. Return `undefined` to leave the
90
+ * mention unchanged.
91
+ *
92
+ * @default `<@U123> (Display Name)`
93
+ */
94
+ formatLabel?: SlackUserMentionLabelFormatter;
95
+ /**
96
+ * Called when the profile resolver throws. Does not include message text.
97
+ */
98
+ onResolveError?: (context: SlackUserMentionEnrichmentErrorContext) => void;
99
+ }
100
+ interface SlackUserMentionEnrichmentResult {
101
+ text: string;
102
+ lookedUpUserIds: string[];
103
+ enrichedUserIds: string[];
104
+ unresolvedUserIds: string[];
105
+ skippedUserIds: string[];
106
+ }
107
+ declare function enrichSlackUserMentions(input: string | SlackUserMentionEnrichmentInput, options?: EnrichSlackUserMentionsOptions): Promise<SlackUserMentionEnrichmentResult>;
108
+
109
+ export { type CreateSlackUserProfileResolverOptions, type EnrichSlackUserMentionsOptions, type SlackUserMentionEnrichmentErrorContext, type SlackUserMentionEnrichmentInput, type SlackUserMentionEnrichmentResult, type SlackUserMentionLabelContext, type SlackUserMentionLabelFormatter, type SlackUserProfile, type SlackUserProfileLookupErrorContext, type SlackUserProfileResolver, type SlackUserProfileResolverInput, type SlackUsersInfoClient, createSlackUserProfileResolver, enrichSlackUserMentions };