UNPKG

n8n

Version:

n8n Workflow Automation Tool

265 lines 10.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getSlackPlatformAgentContext = getSlackPlatformAgentContext; exports.prepareSlackInboundText = prepareSlackInboundText; exports.createSlackBridgeExecutionContext = createSlackBridgeExecutionContext; exports.createSlackResumeExecutionContext = createSlackResumeExecutionContext; const is_record_1 = require("@n8n/utils/is-record"); const SLACK_THINKING_STATUS = 'Thinking...'; const SLACK_STATUS_RETRY_DELAY_MS = 750; const SLACK_HISTORY_MAX_MESSAGES = 30; const SLACK_HISTORY_MAX_CHARS = 8000; const SLACK_HISTORY_MAX_MESSAGE_CHARS = 1500; const SLACK_HISTORY_HEADER = 'Earlier messages in this Slack thread, for context (you were mentioned partway through the conversation):'; const SLACK_HISTORY_OPEN_TAG = '<slack_thread_history>'; const SLACK_HISTORY_CLOSE_TAG = '</slack_thread_history>'; const SLACK_HISTORY_FRAMING_CHARS = SLACK_HISTORY_HEADER.length + SLACK_HISTORY_OPEN_TAG.length + SLACK_HISTORY_CLOSE_TAG.length + 2; function getSlackPlatformAgentContext(chat) { const adapter = chat.getAdapter('slack'); if (!(0, is_record_1.isRecord)(adapter)) return {}; const agentUserId = stringValue(adapter.botUserId); return agentUserId ? { agentUserId } : {}; } function prepareSlackInboundText(text, context) { const trimmed = text.trim(); if (!context.agentUserId) return trimmed; return stripSlackSelfMention(trimmed, context.agentUserId); } async function createSlackBridgeExecutionContext(params) { const platformAgentContext = getSlackPlatformAgentContext(params.chat); const slackThreadContext = getSlackThreadContext(params.message); const shouldFetchHistory = params.isNewMention && slackThreadContext?.hasRealThreadTs === true; const [statusHandle, historyContext] = await Promise.all([ startSlackThinkingStatus(params.thread, { chat: params.chat, logger: params.logger, agentId: params.agentId, slackThreadContext, statusRetry: params.statusRetry, }), shouldFetchHistory ? fetchSlackThreadHistory(params.thread, params.message, platformAgentContext, params.logger, params.agentId) : Promise.resolve(undefined), ]); return { platformAgentContext, forceBuffered: slackThreadContext?.hasRealThreadTs !== true, statusHandle, ...(historyContext ? { historyContext } : {}), }; } async function createSlackResumeExecutionContext(params) { return { forceBuffered: true, statusHandle: await startSlackThinkingStatus(params.thread, { chat: params.chat, logger: params.logger, agentId: params.agentId, }), }; } async function startSlackThinkingStatus(thread, options) { const { slackThreadContext, statusRetry } = options; if (slackThreadContext && !slackThreadContext.hasRealThreadTs) { const setStatus = setSlackAssistantStatus(slackThreadContext, options); return { clearBeforeResponse: async () => { statusRetry?.abort(); await setStatus; await clearSlackAssistantStatus(slackThreadContext, options); }, }; } try { await thread.startTyping(SLACK_THINKING_STATUS); } catch (error) { options.logger.warn('[AgentChatBridge] Failed to set Slack assistant status', { agentId: options.agentId, threadId: thread.id, error: error instanceof Error ? error.message : String(error), }); } return undefined; } async function setSlackAssistantStatus(context, options) { const adapter = getSlackAssistantStatusAdapter(options.chat); if (!adapter) return; await setSlackAssistantStatusWithRetry(adapter, context, options); } async function clearSlackAssistantStatus(context, options) { const adapter = getSlackAssistantStatusAdapter(options.chat); if (!adapter) return; try { await adapter.setAssistantStatus(context.channelId, context.threadTs, ''); } catch (error) { options.logger.warn('[AgentChatBridge] Failed to clear Slack assistant status', { agentId: options.agentId, channelId: context.channelId, threadTs: context.threadTs, error: error instanceof Error ? error.message : String(error), }); } } async function setSlackAssistantStatusWithRetry(adapter, context, options) { try { await adapter.setAssistantStatus(context.channelId, context.threadTs, SLACK_THINKING_STATUS, [ SLACK_THINKING_STATUS, ]); return; } catch (error) { if (getSlackErrorCode(error) !== 'invalid_thread_ts') { options.logger.warn('[AgentChatBridge] Failed to set Slack assistant status', { agentId: options.agentId, channelId: context.channelId, threadTs: context.threadTs, error: error instanceof Error ? error.message : String(error), }); return; } } if (!(await sleep(SLACK_STATUS_RETRY_DELAY_MS, options.statusRetry?.signal))) return; if (options.statusRetry?.signal.aborted) return; try { await adapter.setAssistantStatus(context.channelId, context.threadTs, SLACK_THINKING_STATUS, [ SLACK_THINKING_STATUS, ]); } catch (error) { const errorCode = getSlackErrorCode(error); const logPayload = { agentId: options.agentId, channelId: context.channelId, threadTs: context.threadTs, error: error instanceof Error ? error.message : String(error), ...(errorCode ? { errorCode } : {}), }; if (errorCode === 'invalid_thread_ts') { options.logger.debug('[AgentChatBridge] Slack assistant status unavailable for thread', logPayload); return; } options.logger.warn('[AgentChatBridge] Failed to set Slack assistant status', logPayload); } } async function fetchSlackThreadHistory(thread, triggeringMessage, context, logger, agentId) { try { const triggeringMessageId = triggeringMessage.id; const collected = []; let totalChars = SLACK_HISTORY_FRAMING_CHARS; for await (const message of thread.messages) { if (collected.length >= SLACK_HISTORY_MAX_MESSAGES) break; if (message.id === triggeringMessageId) continue; const text = typeof message.text === 'string' ? message.text.trim() : ''; if (!text) continue; const line = formatSlackHistoryLine(message, text, context); const lineCost = line.length + 1; if (totalChars + lineCost > SLACK_HISTORY_MAX_CHARS) break; collected.push(line); totalChars += lineCost; } if (collected.length === 0) return undefined; const chronological = collected.reverse(); return [ SLACK_HISTORY_HEADER, SLACK_HISTORY_OPEN_TAG, ...chronological, SLACK_HISTORY_CLOSE_TAG, ].join('\n'); } catch (error) { logger.warn('[AgentChatBridge] Failed to fetch Slack thread history', { agentId, threadId: thread.id, error: error instanceof Error ? error.message : String(error), }); return undefined; } } function formatSlackHistoryLine(message, text, context) { const author = message.author; const label = author.userId && context.agentUserId && author.userId === context.agentUserId ? 'you (the agent)' : (author.userName ?? author.userId ?? 'unknown'); const safeText = sanitizeSlackHistoryText(text); const truncatedText = safeText.length > SLACK_HISTORY_MAX_MESSAGE_CHARS ? `${safeText.slice(0, SLACK_HISTORY_MAX_MESSAGE_CHARS)}…` : safeText; return `[${label}]: ${truncatedText}`; } function sanitizeSlackHistoryText(text) { return text.replace(/<(\/?)(slack_thread_history)\s*>/gi, (_m, slash, name) => `[${slash}${name}]`); } function getSlackThreadContext(message) { const raw = message.raw; if (!(0, is_record_1.isRecord)(raw)) return undefined; const channelId = stringValue(raw.channel); const realThreadTs = stringValue(raw.thread_ts); const threadTs = realThreadTs ?? stringValue(raw.ts); if (!channelId || !threadTs) return undefined; return { channelId, threadTs, hasRealThreadTs: realThreadTs !== undefined, }; } function getSlackAssistantStatusAdapter(chat) { const adapter = chat.getAdapter('slack'); return isSlackAssistantStatusAdapter(adapter) ? adapter : undefined; } function stripSlackSelfMention(text, userId) { const escapedUserId = escapeRegExp(userId); return text .replace(new RegExp(`(^|\\s)<@!?${escapedUserId}(?:\\|[^>]+)?>`, 'gi'), '$1') .replace(new RegExp(`(^|\\s)@${escapedUserId}\\b`, 'gi'), '$1') .replace(/\s+/g, ' ') .trim(); } function isSlackAssistantStatusAdapter(value) { return (0, is_record_1.isRecord)(value) && typeof value.setAssistantStatus === 'function'; } function stringValue(value) { return typeof value === 'string' && value.length > 0 ? value : undefined; } function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function getSlackErrorCode(error) { if (!(0, is_record_1.isRecord)(error)) return undefined; const data = error.data; if (!(0, is_record_1.isRecord)(data)) return undefined; return stringValue(data.error); } async function sleep(ms, signal) { if (signal?.aborted) return false; return await new Promise((resolve) => { const timeout = setTimeout(() => { signal?.removeEventListener('abort', abort); resolve(true); }, ms); const abort = () => { clearTimeout(timeout); signal?.removeEventListener('abort', abort); resolve(false); }; signal?.addEventListener('abort', abort, { once: true }); }); } //# sourceMappingURL=slack-bridge-behavior.js.map