n8n
Version:
n8n Workflow Automation Tool
265 lines • 10.4 kB
JavaScript
;
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