@gugu910/pi-slack-bridge 0.1.3

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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +299 -0
  3. package/dist/activity-log.js +304 -0
  4. package/dist/broker/adapters/slack.js +645 -0
  5. package/dist/broker/adapters/types.js +3 -0
  6. package/dist/broker/agent-messaging.js +154 -0
  7. package/dist/broker/auth.js +97 -0
  8. package/dist/broker/client.js +495 -0
  9. package/dist/broker/control-plane-canvas.js +357 -0
  10. package/dist/broker/index.js +125 -0
  11. package/dist/broker/leader.js +133 -0
  12. package/dist/broker/maintenance.js +135 -0
  13. package/dist/broker/paths.js +69 -0
  14. package/dist/broker/router.js +287 -0
  15. package/dist/broker/schema.js +1492 -0
  16. package/dist/broker/socket-server.js +665 -0
  17. package/dist/broker/types.js +12 -0
  18. package/dist/broker-delivery.js +34 -0
  19. package/dist/canvases.js +175 -0
  20. package/dist/deploy-manifest.js +238 -0
  21. package/dist/follower-delivery.js +83 -0
  22. package/dist/git-metadata.js +95 -0
  23. package/dist/guardrails.js +197 -0
  24. package/dist/helpers.js +2128 -0
  25. package/dist/home-tab.js +240 -0
  26. package/dist/index.js +3086 -0
  27. package/dist/pinet-commands.js +244 -0
  28. package/dist/ralph-loop.js +385 -0
  29. package/dist/reaction-triggers.js +160 -0
  30. package/dist/scheduled-wakeups.js +71 -0
  31. package/dist/slack-api.js +5 -0
  32. package/dist/slack-block-kit.js +425 -0
  33. package/dist/slack-export.js +214 -0
  34. package/dist/slack-modals.js +269 -0
  35. package/dist/slack-presence.js +98 -0
  36. package/dist/slack-socket-dedup.js +143 -0
  37. package/dist/slack-tools.js +1715 -0
  38. package/dist/slack-upload.js +147 -0
  39. package/dist/task-assignments.js +403 -0
  40. package/dist/ttl-cache.js +110 -0
  41. package/manifest.yaml +57 -0
  42. package/package.json +45 -0
@@ -0,0 +1,645 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SlackAdapter = exports.RECONNECT_DELAY_MS = void 0;
4
+ exports.parseSocketFrame = parseSocketFrame;
5
+ exports.extractThreadStarted = extractThreadStarted;
6
+ exports.extractAppHomeOpened = extractAppHomeOpened;
7
+ exports.classifyMessage = classifyMessage;
8
+ exports.parseMemberJoinedChannel = parseMemberJoinedChannel;
9
+ const helpers_js_1 = require("../../helpers.js");
10
+ const reaction_triggers_js_1 = require("../../reaction-triggers.js");
11
+ const ttl_cache_js_1 = require("../../ttl-cache.js");
12
+ const slack_socket_dedup_js_1 = require("../../slack-socket-dedup.js");
13
+ const slack_block_kit_js_1 = require("../../slack-block-kit.js");
14
+ /**
15
+ * Parse a raw Socket Mode WebSocket frame into a structured envelope.
16
+ * Returns null if the frame is malformed JSON.
17
+ */
18
+ function parseSocketFrame(raw) {
19
+ try {
20
+ const data = JSON.parse(raw);
21
+ const result = {
22
+ type: data.type ?? "",
23
+ };
24
+ if (data.envelope_id) {
25
+ result.envelopeId = data.envelope_id;
26
+ }
27
+ const dedupKey = (0, slack_socket_dedup_js_1.extractSlackSocketDedupKey)(data);
28
+ if (dedupKey) {
29
+ result.dedupKey = dedupKey;
30
+ }
31
+ if (data.type === "events_api") {
32
+ const payload = data.payload;
33
+ result.event = payload?.event;
34
+ }
35
+ const interactivePayload = (0, slack_block_kit_js_1.extractSlackInteractivePayloadFromEnvelope)(data);
36
+ if (interactivePayload) {
37
+ result.interactivePayload = interactivePayload;
38
+ }
39
+ return result;
40
+ }
41
+ catch {
42
+ /* malformed JSON */
43
+ return null;
44
+ }
45
+ }
46
+ /**
47
+ * Extract thread info from an assistant_thread_started event.
48
+ */
49
+ function extractThreadStarted(evt) {
50
+ const t = evt.assistant_thread;
51
+ if (!t)
52
+ return null;
53
+ const result = {
54
+ channelId: t.channel_id,
55
+ threadTs: t.thread_ts,
56
+ userId: t.user_id,
57
+ };
58
+ const ctx = t.context;
59
+ if (ctx?.channel_id) {
60
+ result.context = { channelId: ctx.channel_id, teamId: ctx.team_id ?? "" };
61
+ }
62
+ return result;
63
+ }
64
+ function extractAppHomeOpened(evt) {
65
+ const userId = typeof evt.user === "string" && evt.user.length > 0 ? evt.user : null;
66
+ if (!userId)
67
+ return null;
68
+ const tab = typeof evt.tab === "string" && evt.tab.length > 0 ? evt.tab : "home";
69
+ return {
70
+ userId,
71
+ tab,
72
+ eventTs: typeof evt.event_ts === "string" && evt.event_ts.length > 0 ? evt.event_ts : null,
73
+ };
74
+ }
75
+ /**
76
+ * Classify an incoming Slack message event. Determines whether the
77
+ * message is relevant (DM, known thread, or bot mention) and
78
+ * extracts the cleaned fields.
79
+ */
80
+ function classifyMessage(evt, botUserId, trackedThreadIds, isKnownThread) {
81
+ // Skip bot messages and messages with subtypes (joins, edits, etc.)
82
+ if (evt.subtype || evt.bot_id)
83
+ return { relevant: false };
84
+ const text = evt.text ?? "";
85
+ const user = evt.user;
86
+ const threadTs = evt.thread_ts;
87
+ const channel = evt.channel;
88
+ const channelType = evt.channel_type;
89
+ const isKnown = !!threadTs && (isKnownThread?.(threadTs) ?? trackedThreadIds.has(threadTs));
90
+ const isDM = channelType === "im";
91
+ const isMention = botUserId != null && text.includes(`<@${botUserId}>`);
92
+ if (!isKnown && !isDM && !isMention)
93
+ return { relevant: false };
94
+ const effectiveTs = threadTs ?? evt.ts;
95
+ const isChannelMention = isMention && !isDM && !isKnown;
96
+ const cleanText = isChannelMention && botUserId ? (0, helpers_js_1.stripBotMention)(text, botUserId) : text;
97
+ return {
98
+ relevant: true,
99
+ threadTs: effectiveTs,
100
+ channel,
101
+ userId: user,
102
+ text: cleanText,
103
+ isDM,
104
+ isChannelMention,
105
+ messageTs: evt.ts ?? effectiveTs,
106
+ };
107
+ }
108
+ /**
109
+ * Parse a member_joined_channel event. Returns null if required fields
110
+ * are missing.
111
+ */
112
+ function parseMemberJoinedChannel(evt, botUserId) {
113
+ const user = evt.user;
114
+ const channel = evt.channel;
115
+ if (!user || !channel)
116
+ return null;
117
+ return { channel, isSelf: user === botUserId };
118
+ }
119
+ // ─── Reconnect delay (exported for testing) ──────────────
120
+ exports.RECONNECT_DELAY_MS = 5000;
121
+ // ─── Slack Adapter ───────────────────────────────────────
122
+ class SlackAdapter {
123
+ name = "slack";
124
+ config;
125
+ allowlist;
126
+ slackRequests = (0, helpers_js_1.createAbortableOperationTracker)();
127
+ botUserId = null;
128
+ ws = null;
129
+ reconnectTimer = null;
130
+ shuttingDown = false;
131
+ inboundHandler = null;
132
+ reactionCommands;
133
+ threads = new Map();
134
+ userNames = new ttl_cache_js_1.TtlCache({
135
+ maxSize: 2000,
136
+ ttlMs: 60 * 60 * 1000,
137
+ });
138
+ processedSocketDeliveries = new ttl_cache_js_1.TtlSet({
139
+ maxSize: slack_socket_dedup_js_1.SLACK_SOCKET_DELIVERY_DEDUP_MAX_SIZE,
140
+ ttlMs: slack_socket_dedup_js_1.SLACK_SOCKET_DELIVERY_DEDUP_TTL_MS,
141
+ });
142
+ pendingEyes = new Map();
143
+ constructor(config) {
144
+ this.config = config;
145
+ this.allowlist = (0, helpers_js_1.buildAllowlist)({ allowedUsers: config.allowedUsers }, undefined);
146
+ this.reactionCommands = (0, reaction_triggers_js_1.resolveReactionCommands)(config.reactionCommands);
147
+ }
148
+ async callSlack(method, token, body) {
149
+ return this.slackRequests.run((signal) => (0, helpers_js_1.callSlackAPI)(method, token, body, { signal }));
150
+ }
151
+ // ─── MessageAdapter interface ─────────────────────────
152
+ async connect() {
153
+ this.shuttingDown = false;
154
+ this.slackRequests = (0, helpers_js_1.createAbortableOperationTracker)();
155
+ const auth = await this.callSlack("auth.test", this.config.botToken);
156
+ this.botUserId = auth.user_id;
157
+ await this.connectSocketMode();
158
+ }
159
+ async disconnect() {
160
+ this.shuttingDown = true;
161
+ if (this.reconnectTimer) {
162
+ clearTimeout(this.reconnectTimer);
163
+ this.reconnectTimer = null;
164
+ }
165
+ try {
166
+ this.ws?.close();
167
+ }
168
+ catch {
169
+ /* ignore close errors */
170
+ }
171
+ this.ws = null;
172
+ await this.slackRequests.abortAndWait();
173
+ }
174
+ onInbound(handler) {
175
+ this.inboundHandler = handler;
176
+ }
177
+ async send(msg) {
178
+ const body = {
179
+ channel: msg.channel,
180
+ text: msg.text,
181
+ thread_ts: msg.threadId,
182
+ };
183
+ if (msg.agentName ?? msg.agentOwnerToken ?? msg.metadata) {
184
+ body.metadata = {
185
+ event_type: "pi_agent_msg",
186
+ event_payload: {
187
+ ...(msg.agentName ? { agent: msg.agentName } : {}),
188
+ ...(msg.agentOwnerToken ? { agent_owner: msg.agentOwnerToken } : {}),
189
+ ...(msg.agentEmoji ? { emoji: msg.agentEmoji } : {}),
190
+ ...msg.metadata,
191
+ },
192
+ };
193
+ }
194
+ await this.callSlack("chat.postMessage", this.config.botToken, body);
195
+ if (this.shuttingDown)
196
+ return;
197
+ // Remove pending eyes for this thread
198
+ const pending = this.pendingEyes.get(msg.threadId);
199
+ if (pending) {
200
+ for (const p of pending) {
201
+ void this.removeReaction(p.channel, p.messageTs, "eyes");
202
+ }
203
+ this.pendingEyes.delete(msg.threadId);
204
+ }
205
+ // Clear thread status
206
+ void this.clearThreadStatus(msg.channel, msg.threadId);
207
+ }
208
+ // ─── Getters (for inspection / testing) ────────────────
209
+ getBotUserId() {
210
+ return this.botUserId;
211
+ }
212
+ getTrackedThreadIds() {
213
+ return new Set(this.threads.keys());
214
+ }
215
+ isConnected() {
216
+ return this.ws?.readyState === WebSocket.OPEN;
217
+ }
218
+ // ─── Socket Mode connection ────────────────────────────
219
+ async connectSocketMode() {
220
+ if (this.shuttingDown)
221
+ return;
222
+ try {
223
+ const res = await this.callSlack("apps.connections.open", this.config.appToken);
224
+ this.ws = new WebSocket(res.url);
225
+ this.ws.addEventListener("message", (event) => {
226
+ void this.handleFrame(String(event.data));
227
+ });
228
+ this.ws.addEventListener("close", () => {
229
+ if (!this.shuttingDown)
230
+ this.scheduleReconnect();
231
+ });
232
+ this.ws.addEventListener("error", () => {
233
+ /* close fires after error — handled there */
234
+ });
235
+ }
236
+ catch (err) {
237
+ if (!(0, helpers_js_1.isAbortError)(err)) {
238
+ console.error(`[slack-adapter] Socket Mode: ${errorMsg(err)}`);
239
+ }
240
+ this.scheduleReconnect();
241
+ }
242
+ }
243
+ async handleFrame(raw) {
244
+ if (this.shuttingDown)
245
+ return;
246
+ const envelope = parseSocketFrame(raw);
247
+ if (!envelope)
248
+ return;
249
+ // ACK every envelope
250
+ if (envelope.envelopeId) {
251
+ this.ws?.send(JSON.stringify({ envelope_id: envelope.envelopeId }));
252
+ }
253
+ const dedupKey = envelope.dedupKey ?? null;
254
+ try {
255
+ if (dedupKey) {
256
+ if (this.processedSocketDeliveries.has(dedupKey)) {
257
+ return;
258
+ }
259
+ this.processedSocketDeliveries.add(dedupKey);
260
+ }
261
+ if (envelope.type === "disconnect") {
262
+ this.scheduleReconnect();
263
+ return;
264
+ }
265
+ if (envelope.interactivePayload) {
266
+ if (envelope.interactivePayload.type === "block_actions") {
267
+ await this.onBlockActions(envelope.interactivePayload);
268
+ }
269
+ else if (envelope.interactivePayload.type === "view_submission") {
270
+ await this.onViewSubmission(envelope.interactivePayload);
271
+ }
272
+ return;
273
+ }
274
+ if (!envelope.event)
275
+ return;
276
+ const evt = envelope.event;
277
+ switch (evt.type) {
278
+ case "assistant_thread_started":
279
+ await this.onThreadStarted(evt);
280
+ break;
281
+ case "assistant_thread_context_changed":
282
+ this.onContextChanged(evt);
283
+ break;
284
+ case "message":
285
+ await this.onMessage(evt);
286
+ break;
287
+ case "reaction_added":
288
+ await this.onReactionAdded(evt);
289
+ break;
290
+ case "member_joined_channel":
291
+ this.onMemberJoined(evt);
292
+ break;
293
+ case "app_home_opened":
294
+ await this.onAppHomeOpened(evt);
295
+ break;
296
+ }
297
+ }
298
+ catch (err) {
299
+ if (dedupKey) {
300
+ this.processedSocketDeliveries.delete(dedupKey);
301
+ }
302
+ throw err;
303
+ }
304
+ }
305
+ // ─── Event handlers ────────────────────────────────────
306
+ async onThreadStarted(evt) {
307
+ if (this.shuttingDown)
308
+ return;
309
+ const parsed = extractThreadStarted(evt);
310
+ if (!parsed)
311
+ return;
312
+ const info = {
313
+ channelId: parsed.channelId,
314
+ threadTs: parsed.threadTs,
315
+ userId: parsed.userId,
316
+ };
317
+ if (parsed.context) {
318
+ info.context = parsed.context;
319
+ }
320
+ this.threads.set(info.threadTs, info);
321
+ try {
322
+ this.config.rememberKnownThread?.(info.threadTs, info.channelId);
323
+ }
324
+ catch {
325
+ /* best effort — DB cache sync must not break Slack event handling */
326
+ }
327
+ await this.setSuggestedPrompts(info.channelId, info.threadTs);
328
+ }
329
+ onContextChanged(evt) {
330
+ if (this.shuttingDown)
331
+ return;
332
+ const t = evt.assistant_thread;
333
+ if (!t)
334
+ return;
335
+ const existing = this.threads.get(t.thread_ts);
336
+ if (!existing)
337
+ return;
338
+ const ctx = t.context;
339
+ if (ctx?.channel_id) {
340
+ existing.context = {
341
+ channelId: ctx.channel_id,
342
+ teamId: ctx.team_id ?? "",
343
+ };
344
+ }
345
+ }
346
+ async onAppHomeOpened(evt) {
347
+ if (this.shuttingDown)
348
+ return;
349
+ const parsed = extractAppHomeOpened(evt);
350
+ if (!parsed || parsed.tab !== "home") {
351
+ return;
352
+ }
353
+ try {
354
+ await this.config.onAppHomeOpened?.(parsed);
355
+ }
356
+ catch (err) {
357
+ console.error(`[slack-adapter] Home tab callback failed: ${errorMsg(err)}`);
358
+ }
359
+ }
360
+ async fetchMessageByTs(channel, messageTs) {
361
+ try {
362
+ const response = await this.callSlack("conversations.history", this.config.botToken, {
363
+ channel,
364
+ oldest: messageTs,
365
+ latest: messageTs,
366
+ inclusive: true,
367
+ limit: 1,
368
+ });
369
+ const messages = response.messages ?? [];
370
+ return messages.find((message) => message.ts === messageTs) ?? messages[0] ?? null;
371
+ }
372
+ catch {
373
+ return null;
374
+ }
375
+ }
376
+ async onReactionAdded(evt) {
377
+ if (this.shuttingDown)
378
+ return;
379
+ const item = evt.item;
380
+ const userId = evt.user;
381
+ const rawReaction = evt.reaction;
382
+ if (!item || item.type !== "message" || !item.channel || !item.ts || !userId || !rawReaction) {
383
+ return;
384
+ }
385
+ if (userId === this.botUserId)
386
+ return;
387
+ let reactionName;
388
+ try {
389
+ reactionName = (0, reaction_triggers_js_1.normalizeReactionName)(rawReaction);
390
+ }
391
+ catch {
392
+ return;
393
+ }
394
+ const command = this.reactionCommands.get(reactionName);
395
+ if (!command || !(0, helpers_js_1.isUserAllowed)(this.allowlist, userId)) {
396
+ return;
397
+ }
398
+ try {
399
+ const reactedMessage = await this.fetchMessageByTs(item.channel, item.ts);
400
+ if (!reactedMessage) {
401
+ throw new Error(`Unable to fetch reacted message ${item.ts} in channel ${item.channel}`);
402
+ }
403
+ const threadTs = reactedMessage.thread_ts ??
404
+ reactedMessage.ts ??
405
+ item.ts;
406
+ if (!this.threads.has(threadTs)) {
407
+ this.threads.set(threadTs, {
408
+ channelId: item.channel,
409
+ threadTs,
410
+ userId: reactedMessage.user ?? userId,
411
+ });
412
+ try {
413
+ this.config.rememberKnownThread?.(threadTs, item.channel);
414
+ }
415
+ catch {
416
+ /* best effort — DB cache sync must not break reaction handling */
417
+ }
418
+ }
419
+ const reactorName = await this.resolveUser(userId);
420
+ if (this.shuttingDown)
421
+ return;
422
+ const reactedMessageAuthorId = reactedMessage.user ?? evt.item_user;
423
+ const reactedMessageAuthor = reactedMessageAuthorId
424
+ ? await this.resolveUser(reactedMessageAuthorId)
425
+ : reactedMessage.bot_id
426
+ ? "bot"
427
+ : "unknown";
428
+ if (this.shuttingDown)
429
+ return;
430
+ const reactedMessageText = typeof reactedMessage.text === "string" && reactedMessage.text.trim().length > 0
431
+ ? reactedMessage.text
432
+ : "(no text)";
433
+ this.inboundHandler?.({
434
+ source: "slack",
435
+ threadId: threadTs,
436
+ channel: item.channel,
437
+ userId,
438
+ userName: reactorName,
439
+ text: (0, reaction_triggers_js_1.buildReactionTriggerMessage)({
440
+ reactionName,
441
+ command,
442
+ reactorName,
443
+ channel: item.channel,
444
+ threadTs,
445
+ messageTs: item.ts,
446
+ reactedMessageText,
447
+ reactedMessageAuthor,
448
+ }),
449
+ timestamp: evt.event_ts ?? item.ts,
450
+ });
451
+ await this.addReaction(item.channel, item.ts, "white_check_mark");
452
+ }
453
+ catch (err) {
454
+ const errorMsg = err instanceof Error ? err.message : String(err);
455
+ console.error(`[slack-adapter] reaction trigger failed: ${errorMsg}`);
456
+ await this.addReaction(item.channel, item.ts, "x");
457
+ }
458
+ }
459
+ async onMessage(evt) {
460
+ if (this.shuttingDown)
461
+ return;
462
+ const classified = classifyMessage(evt, this.botUserId, this.getTrackedThreadIds(), this.config.isKnownThread);
463
+ if (!classified.relevant)
464
+ return;
465
+ const { threadTs, channel, userId, text, isChannelMention, messageTs } = classified;
466
+ // Track thread if new
467
+ if (!this.threads.has(threadTs)) {
468
+ this.threads.set(threadTs, {
469
+ channelId: channel,
470
+ threadTs,
471
+ userId,
472
+ });
473
+ }
474
+ // Allowlist check — silently drop unauthorized users
475
+ if (!(0, helpers_js_1.isUserAllowed)(this.allowlist, userId))
476
+ return;
477
+ // React with eyes to acknowledge
478
+ void this.addReaction(channel, messageTs, "eyes");
479
+ const pending = this.pendingEyes.get(threadTs) ?? [];
480
+ pending.push({ channel, messageTs });
481
+ this.pendingEyes.set(threadTs, pending);
482
+ // Resolve user name
483
+ const userName = await this.resolveUser(userId);
484
+ if (this.shuttingDown)
485
+ return;
486
+ // Emit inbound message
487
+ this.inboundHandler?.({
488
+ source: "slack",
489
+ threadId: threadTs,
490
+ channel,
491
+ userId,
492
+ userName,
493
+ text,
494
+ timestamp: messageTs,
495
+ ...(isChannelMention ? { isChannelMention: true } : {}),
496
+ });
497
+ }
498
+ async emitInteractiveInbound(normalized) {
499
+ if (!this.threads.has(normalized.threadTs)) {
500
+ this.threads.set(normalized.threadTs, {
501
+ channelId: normalized.channel,
502
+ threadTs: normalized.threadTs,
503
+ userId: normalized.userId,
504
+ });
505
+ }
506
+ try {
507
+ this.config.rememberKnownThread?.(normalized.threadTs, normalized.channel);
508
+ }
509
+ catch {
510
+ /* best effort — DB cache sync must not break Slack event handling */
511
+ }
512
+ if (!(0, helpers_js_1.isUserAllowed)(this.allowlist, normalized.userId))
513
+ return;
514
+ const userName = await this.resolveUser(normalized.userId);
515
+ if (this.shuttingDown)
516
+ return;
517
+ this.inboundHandler?.({
518
+ source: "slack",
519
+ threadId: normalized.threadTs,
520
+ channel: normalized.channel,
521
+ userId: normalized.userId,
522
+ userName,
523
+ text: normalized.text,
524
+ timestamp: normalized.timestamp,
525
+ metadata: normalized.metadata,
526
+ });
527
+ }
528
+ async onBlockActions(payload) {
529
+ if (this.shuttingDown)
530
+ return;
531
+ const normalized = (0, slack_block_kit_js_1.normalizeSlackBlockActionPayload)(payload);
532
+ if (!normalized)
533
+ return;
534
+ await this.emitInteractiveInbound(normalized);
535
+ }
536
+ async onViewSubmission(payload) {
537
+ if (this.shuttingDown)
538
+ return;
539
+ const normalized = (0, slack_block_kit_js_1.normalizeSlackViewSubmissionPayload)(payload);
540
+ if (!normalized)
541
+ return;
542
+ await this.emitInteractiveInbound(normalized);
543
+ }
544
+ onMemberJoined(evt) {
545
+ const parsed = parseMemberJoinedChannel(evt, this.botUserId);
546
+ if (!parsed || !parsed.isSelf)
547
+ return;
548
+ this.inboundHandler?.({
549
+ source: "slack",
550
+ threadId: "",
551
+ channel: parsed.channel,
552
+ userId: "system",
553
+ text: `Bot was added to channel ${parsed.channel}`,
554
+ timestamp: String(Date.now() / 1000),
555
+ });
556
+ }
557
+ // ─── Slack API helpers ─────────────────────────────────
558
+ async addReaction(channel, ts, emoji) {
559
+ try {
560
+ await this.callSlack("reactions.add", this.config.botToken, {
561
+ channel,
562
+ timestamp: ts,
563
+ name: emoji,
564
+ });
565
+ }
566
+ catch {
567
+ /* already_reacted or non-critical */
568
+ }
569
+ }
570
+ async removeReaction(channel, ts, emoji) {
571
+ try {
572
+ await this.callSlack("reactions.remove", this.config.botToken, {
573
+ channel,
574
+ timestamp: ts,
575
+ name: emoji,
576
+ });
577
+ }
578
+ catch {
579
+ /* not_reacted or non-critical */
580
+ }
581
+ }
582
+ async resolveUser(userId) {
583
+ const cached = this.userNames.get(userId);
584
+ if (cached)
585
+ return cached;
586
+ try {
587
+ const res = await this.callSlack("users.info", this.config.botToken, {
588
+ user: userId,
589
+ });
590
+ if (this.shuttingDown)
591
+ return userId;
592
+ const u = res.user;
593
+ const name = u.real_name ?? u.name ?? userId;
594
+ this.userNames.set(userId, name);
595
+ return name;
596
+ }
597
+ catch {
598
+ /* non-critical — return raw userId */
599
+ return userId;
600
+ }
601
+ }
602
+ async clearThreadStatus(channelId, threadTs) {
603
+ try {
604
+ await this.callSlack("assistant.threads.setStatus", this.config.botToken, {
605
+ channel_id: channelId,
606
+ thread_ts: threadTs,
607
+ status: "",
608
+ });
609
+ }
610
+ catch {
611
+ /* non-critical */
612
+ }
613
+ }
614
+ async setSuggestedPrompts(channelId, threadTs) {
615
+ const prompts = this.config.suggestedPrompts ?? [
616
+ { title: "Status", message: "What are you working on right now?" },
617
+ { title: "Help", message: "I need help with something in the codebase" },
618
+ { title: "Review", message: "Summarise the recent changes" },
619
+ ];
620
+ try {
621
+ await this.callSlack("assistant.threads.setSuggestedPrompts", this.config.botToken, {
622
+ channel_id: channelId,
623
+ thread_ts: threadTs,
624
+ prompts,
625
+ });
626
+ }
627
+ catch {
628
+ /* non-critical */
629
+ }
630
+ }
631
+ // ─── Reconnect ─────────────────────────────────────────
632
+ scheduleReconnect() {
633
+ if (this.shuttingDown || this.reconnectTimer)
634
+ return;
635
+ this.reconnectTimer = setTimeout(() => {
636
+ this.reconnectTimer = null;
637
+ void this.connectSocketMode();
638
+ }, exports.RECONNECT_DELAY_MS);
639
+ }
640
+ }
641
+ exports.SlackAdapter = SlackAdapter;
642
+ // ─── Utility ─────────────────────────────────────────────
643
+ function errorMsg(err) {
644
+ return err instanceof Error ? err.message : String(err);
645
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // ─── Adapter message types ───────────────────────────────
3
+ Object.defineProperty(exports, "__esModule", { value: true });