@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,747 @@
1
+ import {
2
+ extractSlackMessageText
3
+ } from "./chunk-FPCE5V5Y.js";
4
+
5
+ // src/history/context/assistant-root.ts
6
+ function normalizeAssistantSurfaceThreadRootMessages(messages, options) {
7
+ const rootUser = options.assistantSurfaceThreadRootUser;
8
+ const threadTs = options.activity.threadTs;
9
+ if (!rootUser?.userId || !threadTs || messages.length === 0) {
10
+ return [...messages];
11
+ }
12
+ const firstMessage = messages[0];
13
+ if (!firstMessage || firstMessage.role !== "assistant") {
14
+ return [...messages];
15
+ }
16
+ const text = firstMessage.text.trim();
17
+ if (!text || isLikelyAssistantGreeting(text)) {
18
+ return [...messages];
19
+ }
20
+ const duplicateIndex = messages.findIndex(
21
+ (message, index) => index > 0 && message.userId === rootUser.userId && normalizeHistoryText(message.text) === normalizeHistoryText(text)
22
+ );
23
+ if (duplicateIndex >= 0) {
24
+ options.onAssistantSurfaceThreadRootNormalized?.({
25
+ action: "removed-duplicate",
26
+ channelId: options.activity.channelId,
27
+ ...options.teamId ?? options.activity.teamId ? { teamId: options.teamId ?? options.activity.teamId } : {},
28
+ threadTs,
29
+ userId: rootUser.userId
30
+ });
31
+ return messages.slice(1);
32
+ }
33
+ options.onAssistantSurfaceThreadRootNormalized?.({
34
+ action: "corrected-author",
35
+ channelId: options.activity.channelId,
36
+ ...options.teamId ?? options.activity.teamId ? { teamId: options.teamId ?? options.activity.teamId } : {},
37
+ threadTs,
38
+ userId: rootUser.userId
39
+ });
40
+ return [
41
+ {
42
+ ...firstMessage,
43
+ userId: rootUser.userId,
44
+ botId: void 0,
45
+ appId: void 0,
46
+ role: "user",
47
+ isBot: false
48
+ },
49
+ ...messages.slice(1)
50
+ ];
51
+ }
52
+ function normalizeHistoryText(value) {
53
+ return value.trim().replace(/\s+/g, " ");
54
+ }
55
+ function isLikelyAssistantGreeting(text) {
56
+ const normalized = text.trim().toLowerCase();
57
+ return normalized === "how can i help?" || normalized === "how can i help" || normalized === "hi, how can i help?" || normalized === "hi, how can i help";
58
+ }
59
+
60
+ // src/history/reader.ts
61
+ var DEFAULT_HISTORY_LIMIT = 15;
62
+ var DEFAULT_HEADER = "Slack conversation history:";
63
+ var DEFAULT_MAX_CHARACTERS = 12e3;
64
+ var DEFAULT_ROLE_LABELS = {
65
+ assistant: "Assistant",
66
+ bot: "Bot",
67
+ system: "System",
68
+ unknown: "Message",
69
+ user: "User"
70
+ };
71
+ async function readSlackThreadHistory(options) {
72
+ const client = resolveConversationsClient(options.client);
73
+ const response = await client.replies({
74
+ channel: options.channelId,
75
+ ts: options.threadTs,
76
+ limit: options.limit ?? DEFAULT_HISTORY_LIMIT,
77
+ ...options.cursor ? { cursor: options.cursor } : {},
78
+ ...options.token ? { token: options.token } : {},
79
+ ...options.includeAllMetadata !== void 0 ? { include_all_metadata: options.includeAllMetadata } : {},
80
+ ...options.oldest ? { oldest: options.oldest } : {},
81
+ ...options.latest ? { latest: options.latest } : {},
82
+ ...options.inclusive !== void 0 ? { inclusive: options.inclusive } : {}
83
+ });
84
+ assertSlackHistoryResponseOk(response, "conversations.replies");
85
+ return toHistoryPage(response, options.channelId, options.botUserId);
86
+ }
87
+ async function readSlackChannelHistory(options) {
88
+ const client = resolveConversationsClient(options.client);
89
+ const response = await client.history({
90
+ channel: options.channelId,
91
+ limit: options.limit ?? DEFAULT_HISTORY_LIMIT,
92
+ ...options.cursor ? { cursor: options.cursor } : {},
93
+ ...options.token ? { token: options.token } : {},
94
+ ...options.includeAllMetadata !== void 0 ? { include_all_metadata: options.includeAllMetadata } : {},
95
+ ...options.oldest ? { oldest: options.oldest } : {},
96
+ ...options.latest ? { latest: options.latest } : {},
97
+ ...options.inclusive !== void 0 ? { inclusive: options.inclusive } : {}
98
+ });
99
+ assertSlackHistoryResponseOk(response, "conversations.history");
100
+ const page = toHistoryPage(response, options.channelId, options.botUserId);
101
+ return options.oldestFirst === false ? page : { ...page, messages: [...page.messages].reverse() };
102
+ }
103
+ function formatSlackHistoryForPrompt(messages, options = {}) {
104
+ const selected = options.maxMessages && options.maxMessages > 0 ? messages.slice(-options.maxMessages) : messages;
105
+ const labels = { ...DEFAULT_ROLE_LABELS, ...options.roleLabels ?? {} };
106
+ const lines = selected.filter((message) => message.text.trim().length > 0).map((message) => {
107
+ const label = normalizeAuthorLabel(options.formatAuthor?.(message)) ?? labels[message.role];
108
+ const timestamp = options.includeTimestamps && message.ts ? ` [${message.ts}]` : "";
109
+ return `${label}${timestamp}: ${message.text.trim()}`;
110
+ });
111
+ const header = options.header === false ? void 0 : options.header ?? DEFAULT_HEADER;
112
+ const text = header ? [header, ...lines].join("\n") : lines.join("\n");
113
+ const maxCharacters = options.maxCharacters ?? DEFAULT_MAX_CHARACTERS;
114
+ if (maxCharacters <= 0 || text.length <= maxCharacters) {
115
+ return text;
116
+ }
117
+ return text.slice(0, maxCharacters).trimEnd();
118
+ }
119
+ function toHistoryPage(response, channelId, botUserId) {
120
+ const messages = (response.messages ?? []).map(
121
+ (message) => normalizeSlackHistoryMessage(message, channelId, botUserId)
122
+ ).filter((message) => message !== void 0);
123
+ const nextCursor = response.response_metadata?.next_cursor;
124
+ return {
125
+ messages,
126
+ ...nextCursor ? { nextCursor } : {},
127
+ hasMore: response.has_more === true || Boolean(nextCursor)
128
+ };
129
+ }
130
+ function normalizeSlackHistoryMessage(message, channelId, botUserId) {
131
+ if (!message.ts) {
132
+ return void 0;
133
+ }
134
+ const userId = readString(message.user);
135
+ const botId = readString(message.bot_id);
136
+ const appId = readString(message.app_id);
137
+ const subtype = readString(readMessageField(message, "subtype"));
138
+ const text = extractSlackMessageText(message);
139
+ const isBot = Boolean(botId) || Boolean(message.bot_profile) || Boolean(botUserId) && userId === botUserId;
140
+ return {
141
+ channelId,
142
+ ts: message.ts,
143
+ ...message.thread_ts ? { threadTs: message.thread_ts } : {},
144
+ ...userId ? { userId } : {},
145
+ ...botId ? { botId } : {},
146
+ ...appId ? { appId } : {},
147
+ ...subtype ? { subtype } : {},
148
+ text,
149
+ role: resolveHistoryRole({ userId, botId, subtype, isBot, botUserId }),
150
+ isBot,
151
+ raw: message
152
+ };
153
+ }
154
+ function resolveHistoryRole(context) {
155
+ if (context.botUserId && context.userId === context.botUserId) {
156
+ return "assistant";
157
+ }
158
+ if (context.botId || context.isBot) {
159
+ return "bot";
160
+ }
161
+ if (context.userId) {
162
+ return "user";
163
+ }
164
+ if (context.subtype) {
165
+ return "system";
166
+ }
167
+ return "unknown";
168
+ }
169
+ function assertSlackHistoryResponseOk(response, method) {
170
+ if (response.ok === false) {
171
+ throw new Error(
172
+ `${method} failed${response.error ? `: ${response.error}` : ""}`
173
+ );
174
+ }
175
+ }
176
+ function readString(value) {
177
+ return typeof value === "string" && value.length > 0 ? value : void 0;
178
+ }
179
+ function normalizeAuthorLabel(value) {
180
+ const trimmed = value?.trim();
181
+ return trimmed || void 0;
182
+ }
183
+ function resolveConversationsClient(client) {
184
+ if ("conversations" in client) {
185
+ return client.conversations;
186
+ }
187
+ return client;
188
+ }
189
+ function readMessageField(message, field) {
190
+ return message[field];
191
+ }
192
+
193
+ // src/history/context/errors.ts
194
+ var EXPECTED_SLACK_HISTORY_ERRORS = {
195
+ access_denied: "Slack denied access to the conversation history.",
196
+ channel_not_found: "The bot could not find this Slack conversation or is not a member.",
197
+ missing_scope: "The Slack token is missing a history scope for this conversation type.",
198
+ no_permission: "The bot does not have permission to read this conversation.",
199
+ not_in_channel: "The bot is not a member of this channel and cannot read its history."
200
+ };
201
+ function toUnavailable(context) {
202
+ const info = inspectSlackHistoryError(context.error);
203
+ return {
204
+ source: context.source,
205
+ method: context.method,
206
+ channelId: context.channelId,
207
+ ...info
208
+ };
209
+ }
210
+ function inspectSlackHistoryError(error) {
211
+ const message = error instanceof Error ? error.message : String(error);
212
+ const errorCode = readSlackErrorCode(message);
213
+ const hint = errorCode ? EXPECTED_SLACK_HISTORY_ERRORS[errorCode] : void 0;
214
+ return {
215
+ message,
216
+ ...errorCode ? { errorCode } : {},
217
+ expected: Boolean(hint),
218
+ ...hint ? { hint } : {}
219
+ };
220
+ }
221
+ function readSlackErrorCode(message) {
222
+ const normalized = message.trim();
223
+ const match = /failed:\s*([a-z_]+)/i.exec(normalized);
224
+ if (match?.[1]) {
225
+ return match[1];
226
+ }
227
+ return EXPECTED_SLACK_HISTORY_ERRORS[normalized] ? normalized : void 0;
228
+ }
229
+
230
+ // src/history/context/fetch.ts
231
+ function shouldReadTopLevelChannelHistory(options, threadMessages) {
232
+ if (options.includeTopLevelChannelFallback === false) {
233
+ return false;
234
+ }
235
+ return !options.activity.threadTs || threadMessages.length === 0;
236
+ }
237
+ function shouldReadOriginChannelHistory(options, topLevelChannelMessages) {
238
+ return Boolean(
239
+ options.originChannelId && options.originChannelId !== options.activity.channelId && topLevelChannelMessages.length === 0
240
+ );
241
+ }
242
+ async function readThreadMessages(options, unavailable) {
243
+ const threadTs = options.activity.threadTs;
244
+ if (!threadTs) {
245
+ return [];
246
+ }
247
+ try {
248
+ const page = await readSlackThreadHistory({
249
+ client: options.client,
250
+ channelId: options.activity.channelId,
251
+ threadTs,
252
+ botUserId: options.botUserId,
253
+ token: options.token,
254
+ limit: options.limit
255
+ });
256
+ return filterCurrentActivity(page.messages, options.activity);
257
+ } catch (error) {
258
+ unavailable.push(
259
+ toUnavailable({
260
+ source: "thread",
261
+ method: "conversations.replies",
262
+ channelId: options.activity.channelId,
263
+ error
264
+ })
265
+ );
266
+ return [];
267
+ }
268
+ }
269
+ async function readTopLevelChannelMessages(options, unavailable) {
270
+ try {
271
+ const page = await readSlackChannelHistory({
272
+ client: options.client,
273
+ channelId: options.activity.channelId,
274
+ botUserId: options.botUserId,
275
+ token: options.token,
276
+ limit: options.limit,
277
+ latest: options.activity.messageTs,
278
+ inclusive: false
279
+ });
280
+ return filterCurrentActivity(page.messages, options.activity);
281
+ } catch (error) {
282
+ unavailable.push(
283
+ toUnavailable({
284
+ source: "channel",
285
+ method: "conversations.history",
286
+ channelId: options.activity.channelId,
287
+ error
288
+ })
289
+ );
290
+ return [];
291
+ }
292
+ }
293
+ async function readOriginChannelMessages(options, unavailable) {
294
+ const channelId = options.originChannelId;
295
+ if (!channelId) {
296
+ return [];
297
+ }
298
+ try {
299
+ const page = await readSlackChannelHistory({
300
+ client: options.client,
301
+ channelId,
302
+ botUserId: options.botUserId,
303
+ token: options.token,
304
+ limit: options.limit,
305
+ latest: options.activity.messageTs,
306
+ inclusive: false
307
+ });
308
+ return page.messages;
309
+ } catch (error) {
310
+ unavailable.push(
311
+ toUnavailable({
312
+ source: "origin-channel",
313
+ method: "conversations.history",
314
+ channelId,
315
+ error
316
+ })
317
+ );
318
+ return [];
319
+ }
320
+ }
321
+ function filterCurrentActivity(messages, activity) {
322
+ return messages.filter((message) => message.ts !== activity.messageTs);
323
+ }
324
+
325
+ // src/history/context/format.ts
326
+ async function formatHistorySection(messages, options) {
327
+ if (messages.length === 0 || !messages.some((message) => message.text.trim().length > 0)) {
328
+ return void 0;
329
+ }
330
+ const authorContext = toAuthorResolverContext(options);
331
+ const formatAuthor = await createResolvedAuthorFormatter(
332
+ messages,
333
+ options.resolveAuthorLabel,
334
+ authorContext
335
+ );
336
+ return formatSlackHistoryForPrompt(messages, {
337
+ header: options.header,
338
+ maxCharacters: options.maxCharacters,
339
+ ...formatAuthor ? { formatAuthor } : {}
340
+ });
341
+ }
342
+ function boundText(text, maxCharacters) {
343
+ const trimmed = text.trim();
344
+ if (!trimmed || maxCharacters === void 0 || maxCharacters <= 0 || trimmed.length <= maxCharacters) {
345
+ return trimmed;
346
+ }
347
+ return trimmed.slice(0, maxCharacters).trimEnd();
348
+ }
349
+ function createCachedAuthorLabelResolver(resolveAuthor, onResolveAuthorError) {
350
+ if (!resolveAuthor) {
351
+ return void 0;
352
+ }
353
+ const authorByKey = /* @__PURE__ */ new Map();
354
+ return async (message, context) => {
355
+ const key = authorCacheKey(message, context);
356
+ if (!key) {
357
+ return resolveAuthorSafely(
358
+ message,
359
+ context,
360
+ resolveAuthor,
361
+ onResolveAuthorError
362
+ );
363
+ }
364
+ let cached = authorByKey.get(key);
365
+ if (!cached) {
366
+ cached = resolveAuthorSafely(
367
+ message,
368
+ context,
369
+ resolveAuthor,
370
+ onResolveAuthorError
371
+ );
372
+ authorByKey.set(key, cached);
373
+ }
374
+ return cached;
375
+ };
376
+ }
377
+ function toAuthorResolverContext(context) {
378
+ return {
379
+ source: context.source,
380
+ channelId: context.channelId,
381
+ ...context.teamId ? { teamId: context.teamId } : {}
382
+ };
383
+ }
384
+ async function resolveAuthorSafely(message, context, resolveAuthor, onResolveAuthorError) {
385
+ try {
386
+ return normalizeAuthorLabel2(await resolveAuthor(message, context));
387
+ } catch (error) {
388
+ onResolveAuthorError?.({
389
+ ...context,
390
+ ...message.userId ? { userId: message.userId } : {},
391
+ error
392
+ });
393
+ return void 0;
394
+ }
395
+ }
396
+ async function createResolvedAuthorFormatter(messages, resolveAuthorLabel, context) {
397
+ if (!resolveAuthorLabel) {
398
+ return void 0;
399
+ }
400
+ const authorByMessage = /* @__PURE__ */ new Map();
401
+ await Promise.all(
402
+ messages.map(async (message) => {
403
+ const author = await resolveAuthorLabel(message, context);
404
+ if (author) {
405
+ authorByMessage.set(message, author);
406
+ }
407
+ })
408
+ );
409
+ if (authorByMessage.size === 0) {
410
+ return void 0;
411
+ }
412
+ return (message) => authorByMessage.get(message);
413
+ }
414
+ function normalizeAuthorLabel2(value) {
415
+ const trimmed = value?.trim();
416
+ return trimmed || void 0;
417
+ }
418
+ function authorCacheKey(message, context) {
419
+ if (message.userId) {
420
+ return `${context.teamId ?? "unknown-team"}:${message.role}:user:${message.userId}`;
421
+ }
422
+ if (message.botId) {
423
+ return `${context.teamId ?? "unknown-team"}:${message.role}:bot:${message.botId}`;
424
+ }
425
+ if (message.appId) {
426
+ return `${context.teamId ?? "unknown-team"}:${message.role}:app:${message.appId}`;
427
+ }
428
+ return void 0;
429
+ }
430
+
431
+ // src/history/context/visibility.ts
432
+ async function applyHistoryVisibilityPolicy({
433
+ options,
434
+ originChannelMessages,
435
+ threadMessages,
436
+ topLevelChannelMessages
437
+ }) {
438
+ const teamId = options.teamId ?? options.activity.teamId;
439
+ const thread = await applyHistoryVisibilityPolicyForSource({
440
+ options,
441
+ messages: threadMessages,
442
+ source: "thread",
443
+ channelId: options.activity.channelId,
444
+ ...teamId ? { teamId } : {}
445
+ });
446
+ const topLevelChannel = await applyHistoryVisibilityPolicyForSource({
447
+ options,
448
+ messages: topLevelChannelMessages,
449
+ source: "channel",
450
+ channelId: options.activity.channelId,
451
+ ...teamId ? { teamId } : {}
452
+ });
453
+ const originChannel = await applyHistoryVisibilityPolicyForSource({
454
+ options,
455
+ messages: originChannelMessages,
456
+ source: "origin-channel",
457
+ channelId: options.originChannelId ?? options.activity.channelId,
458
+ ...teamId ? { teamId } : {}
459
+ });
460
+ const decisions = [thread, topLevelChannel, originChannel];
461
+ return {
462
+ decisions,
463
+ omittedTotal: decisions.reduce(
464
+ (total, decision) => total + decision.omittedCount,
465
+ 0
466
+ ),
467
+ originChannel,
468
+ thread,
469
+ topLevelChannel
470
+ };
471
+ }
472
+ async function applyHistoryVisibilityPolicyForSource({
473
+ channelId,
474
+ messages,
475
+ options,
476
+ source,
477
+ teamId
478
+ }) {
479
+ const base = {
480
+ channelId,
481
+ source,
482
+ ...teamId ? { teamId } : {}
483
+ };
484
+ if (!options.visibilityPolicy) {
485
+ return {
486
+ messages: [...messages],
487
+ mode: "all",
488
+ omittedCount: 0,
489
+ source
490
+ };
491
+ }
492
+ try {
493
+ return await options.visibilityPolicy.filter(messages, {
494
+ activity: options.activity,
495
+ botUserId: options.botUserId,
496
+ ...base
497
+ });
498
+ } catch (error) {
499
+ options.onVisibilityPolicyError?.({
500
+ ...base,
501
+ error
502
+ });
503
+ return {
504
+ messages: [],
505
+ mode: "error",
506
+ omittedCount: messages.length,
507
+ source
508
+ };
509
+ }
510
+ }
511
+
512
+ // src/history/context/load.ts
513
+ var DEFAULT_HISTORY_CONTEXT_MAX_CHARACTERS = 12e3;
514
+ async function loadSlackTurnHistoryContext(options) {
515
+ const maxCharacters = options.maxCharacters ?? DEFAULT_HISTORY_CONTEXT_MAX_CHARACTERS;
516
+ const unavailable = [];
517
+ const threadMessages = normalizeAssistantSurfaceThreadRootMessages(
518
+ await readThreadMessages(options, unavailable),
519
+ options
520
+ );
521
+ const topLevelChannelMessages = shouldReadTopLevelChannelHistory(
522
+ options,
523
+ threadMessages
524
+ ) ? await readTopLevelChannelMessages(options, unavailable) : [];
525
+ const originChannelMessages = shouldReadOriginChannelHistory(
526
+ options,
527
+ topLevelChannelMessages
528
+ ) ? await readOriginChannelMessages(options, unavailable) : [];
529
+ const visibility = await applyHistoryVisibilityPolicy({
530
+ options,
531
+ threadMessages,
532
+ topLevelChannelMessages,
533
+ originChannelMessages
534
+ });
535
+ const visibleThreadMessages = visibility.thread.messages;
536
+ const visibleTopLevelChannelMessages = visibility.topLevelChannel.messages;
537
+ const visibleOriginChannelMessages = visibility.originChannel.messages;
538
+ const resolveAuthorLabel = createCachedAuthorLabelResolver(
539
+ options.resolveAuthor,
540
+ options.onResolveAuthorError
541
+ );
542
+ const teamId = options.teamId ?? options.activity.teamId;
543
+ const sections = (await Promise.all([
544
+ formatHistorySection(visibleThreadMessages, {
545
+ source: "thread",
546
+ channelId: options.activity.channelId,
547
+ ...teamId ? { teamId } : {},
548
+ header: options.headers?.thread ?? "Slack thread history:",
549
+ maxCharacters,
550
+ resolveAuthorLabel
551
+ }),
552
+ formatHistorySection(visibleTopLevelChannelMessages, {
553
+ source: "channel",
554
+ channelId: options.activity.channelId,
555
+ ...teamId ? { teamId } : {},
556
+ header: options.headers?.channel ?? "Recent Slack channel history:",
557
+ maxCharacters,
558
+ resolveAuthorLabel
559
+ }),
560
+ formatHistorySection(visibleOriginChannelMessages, {
561
+ source: "origin-channel",
562
+ channelId: options.originChannelId ?? options.activity.channelId,
563
+ ...teamId ? { teamId } : {},
564
+ header: options.headers?.originChannel ?? "Recent Slack origin channel history:",
565
+ maxCharacters,
566
+ resolveAuthorLabel
567
+ })
568
+ ])).filter((section) => Boolean(section));
569
+ const prompt = boundText(sections.join("\n\n"), maxCharacters);
570
+ return {
571
+ ...prompt ? {
572
+ prompt,
573
+ fragment: {
574
+ content: prompt,
575
+ title: "Slack Conversation History",
576
+ source: "slack",
577
+ kind: "slack-context",
578
+ role: "user",
579
+ placement: "after-latest-user",
580
+ lifetime: "turn",
581
+ budgetBehavior: "droppable",
582
+ maxChars: maxCharacters,
583
+ metadata: {
584
+ threadMessageCount: visibleThreadMessages.length,
585
+ topLevelChannelMessageCount: visibleTopLevelChannelMessages.length,
586
+ originChannelMessageCount: visibleOriginChannelMessages.length,
587
+ visibilityOmittedMessageCount: visibility.omittedTotal,
588
+ threadVisibilityOmittedMessageCount: visibility.thread.omittedCount,
589
+ topLevelChannelVisibilityOmittedMessageCount: visibility.topLevelChannel.omittedCount,
590
+ originChannelVisibilityOmittedMessageCount: visibility.originChannel.omittedCount,
591
+ unavailableCount: unavailable.length
592
+ }
593
+ }
594
+ } : {},
595
+ threadMessageCount: visibleThreadMessages.length,
596
+ topLevelChannelMessageCount: visibleTopLevelChannelMessages.length,
597
+ originChannelMessageCount: visibleOriginChannelMessages.length,
598
+ threadVisibilityOmittedMessageCount: visibility.thread.omittedCount,
599
+ topLevelChannelVisibilityOmittedMessageCount: visibility.topLevelChannel.omittedCount,
600
+ originChannelVisibilityOmittedMessageCount: visibility.originChannel.omittedCount,
601
+ visibilityOmittedMessageCount: visibility.omittedTotal,
602
+ visibility: visibility.decisions,
603
+ unavailable
604
+ };
605
+ }
606
+
607
+ // src/history/inclusion-policy.ts
608
+ function createSlackSupplementalHistoryPolicy({
609
+ logger,
610
+ sessionTranscript
611
+ } = {}) {
612
+ const seenDirectSessionIds = /* @__PURE__ */ new Set();
613
+ const transcriptCoveredDirectSessionIds = /* @__PURE__ */ new Set();
614
+ async function hasPersistedConversationMessages(request, sessionId) {
615
+ if (transcriptCoveredDirectSessionIds.has(sessionId)) {
616
+ return true;
617
+ }
618
+ if (!sessionTranscript) {
619
+ return void 0;
620
+ }
621
+ try {
622
+ const hasMessages = await sessionTranscript.hasConversationMessages(sessionId);
623
+ if (hasMessages) {
624
+ transcriptCoveredDirectSessionIds.add(sessionId);
625
+ }
626
+ return hasMessages;
627
+ } catch (error) {
628
+ logger?.debug?.("Slack session transcript check failed", {
629
+ channelId: request.slackActivity.channelId,
630
+ channelType: request.slackActivity.channelType,
631
+ error: formatError(error),
632
+ sessionId,
633
+ teamId: request.slackActivity.teamId,
634
+ threadTs: request.slackActivity.threadTs
635
+ });
636
+ return void 0;
637
+ }
638
+ }
639
+ return {
640
+ async decide(request) {
641
+ if (request.slackActivity.channelType !== "dm") {
642
+ return {
643
+ include: true,
644
+ reason: "shared-slack-surface",
645
+ source: "shared-surface"
646
+ };
647
+ }
648
+ const sessionId = request.sessionId;
649
+ if (sessionId) {
650
+ const transcriptCoversTurn = await hasPersistedConversationMessages(
651
+ request,
652
+ sessionId
653
+ );
654
+ if (transcriptCoversTurn === true) {
655
+ return {
656
+ include: false,
657
+ reason: "session-transcript-covers-direct-turn",
658
+ source: "session-transcript"
659
+ };
660
+ }
661
+ if (transcriptCoversTurn === false) {
662
+ return {
663
+ include: true,
664
+ reason: "first-direct-session-turn",
665
+ source: "session-transcript"
666
+ };
667
+ }
668
+ }
669
+ if (!sessionId || !seenDirectSessionIds.has(sessionId)) {
670
+ if (sessionId) {
671
+ seenDirectSessionIds.add(sessionId);
672
+ }
673
+ return {
674
+ include: true,
675
+ reason: "first-direct-session-turn",
676
+ source: "process-memory"
677
+ };
678
+ }
679
+ return {
680
+ include: false,
681
+ reason: "session-transcript-covers-direct-turn",
682
+ source: "process-memory"
683
+ };
684
+ }
685
+ };
686
+ }
687
+ function formatError(error) {
688
+ return error instanceof Error ? error.message : String(error);
689
+ }
690
+
691
+ // src/history/visibility-policy.ts
692
+ function createSlackSupplementalHistoryVisibilityPolicy({
693
+ allowedUserIds = [],
694
+ mode = "all"
695
+ } = {}) {
696
+ const allowedUsers = new Set(
697
+ Array.from(allowedUserIds, (userId) => userId.trim()).filter(Boolean)
698
+ );
699
+ return {
700
+ async filter(messages, context) {
701
+ if (mode === "all") {
702
+ return {
703
+ messages: [...messages],
704
+ mode,
705
+ omittedCount: 0,
706
+ source: context.source
707
+ };
708
+ }
709
+ const filtered = messages.filter(
710
+ (message) => isVisibleSlackHistoryMessage(message, context, mode, allowedUsers)
711
+ );
712
+ return {
713
+ messages: filtered,
714
+ mode,
715
+ omittedCount: messages.length - filtered.length,
716
+ source: context.source
717
+ };
718
+ }
719
+ };
720
+ }
721
+ function isVisibleSlackHistoryMessage(message, context, mode, allowedUsers) {
722
+ if (isAssistantHistoryMessage(message, context.botUserId)) {
723
+ return true;
724
+ }
725
+ if (!message.userId) {
726
+ return false;
727
+ }
728
+ if (mode === "allowed-users-plus-assistant") {
729
+ return allowedUsers.has(message.userId);
730
+ }
731
+ if (mode === "current-user-plus-assistant") {
732
+ return message.userId === context.activity.userId;
733
+ }
734
+ const originalUserId = context.activity.parentUserId ?? context.activity.userId;
735
+ return message.userId === originalUserId;
736
+ }
737
+ function isAssistantHistoryMessage(message, botUserId) {
738
+ return message.role === "assistant" || Boolean(botUserId && message.userId === botUserId);
739
+ }
740
+ export {
741
+ createSlackSupplementalHistoryPolicy,
742
+ createSlackSupplementalHistoryVisibilityPolicy,
743
+ formatSlackHistoryForPrompt,
744
+ loadSlackTurnHistoryContext,
745
+ readSlackChannelHistory,
746
+ readSlackThreadHistory
747
+ };