From 3f010579c2b6d0e78796f3f9a10249c1e6587c02 Mon Sep 17 00:00:00 2001 From: gimenes Date: Mon, 23 Mar 2026 10:47:04 -0300 Subject: [PATCH 1/5] chore(slack): add verbose debug logging across message handling flow Add console.logs to all strategic points in the Slack message handling pipeline to aid production debugging. Covers: webhook routing, event filtering, event handling, context building, LLM calls, streaming, Decopilot API, and Slack API responses. Includes timing, message previews, config details, and error context at each step. Co-Authored-By: Claude Opus 4.6 (1M context) --- shared/mesh-chat/client.ts | 17 ++++ shared/mesh-chat/generate.ts | 20 ++++- slack-mcp/server/lib/llm.ts | 53 +++++++++++-- slack-mcp/server/lib/slack-client.ts | 33 ++++++-- slack-mcp/server/router.ts | 32 +++++++- .../server/slack/handlers/context-builder.ts | 19 +++++ .../server/slack/handlers/eventHandler.ts | 77 ++++++++++++++++--- .../server/slack/handlers/llm-handler.ts | 33 ++++++++ slack-mcp/server/webhook.ts | 12 ++- 9 files changed, 266 insertions(+), 30 deletions(-) diff --git a/shared/mesh-chat/client.ts b/shared/mesh-chat/client.ts index 826f0812..4af1831a 100644 --- a/shared/mesh-chat/client.ts +++ b/shared/mesh-chat/client.ts @@ -73,10 +73,23 @@ export async function callDecopilotAPI( toolApprovalLevel: "yolo" as const, }; + console.log(`[MeshChat] ========== callDecopilotAPI ==========`); + console.log(`[MeshChat] URL: ${url}`); + console.log(`[MeshChat] Model: ${modelId}, Provider: ${resolveProvider(modelId)}, Connection: ${modelProviderId ?? "none"}`); + console.log(`[MeshChat] Agent: id=${agentId ?? "none"}, mode=${agentMode}`); + console.log(`[MeshChat] Messages: ${messages.length}, timeout: ${timeoutMs}ms`); + console.log(`[MeshChat] Effective mesh URL: ${effectiveMeshUrl} (original: ${meshUrl})`); + messages.forEach((m, i) => { + const partsDesc = m.parts.map(p => p.type === "text" ? `text(${(p as any).text?.length ?? 0})` : `file(${(p as any).filename ?? "?"})`).join(", "); + console.log(`[MeshChat] Message[${i}]: role=${m.role}, parts=[${partsDesc}]`); + }); + const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), timeoutMs); + const fetchStartTime = Date.now(); try { + console.log(`[MeshChat] Sending request to Decopilot API...`); const response = await fetch(url, { method: "POST", headers: { @@ -88,8 +101,12 @@ export async function callDecopilotAPI( signal: controller.signal, }); + console.log(`[MeshChat] Decopilot API response: status=${response.status}, ok=${response.ok}, time=${Date.now() - fetchStartTime}ms`); + console.log(`[MeshChat] Response headers: content-type=${response.headers.get("content-type")}`); + if (!response.ok) { const errorText = await response.text(); + console.error(`[MeshChat] Decopilot API FAILED: status=${response.status}, body=${errorText.substring(0, 500)}`); throw new Error( `Decopilot API call failed (${response.status}): ${errorText}`, ); diff --git a/shared/mesh-chat/generate.ts b/shared/mesh-chat/generate.ts index c01c4d54..d9a9552b 100644 --- a/shared/mesh-chat/generate.ts +++ b/shared/mesh-chat/generate.ts @@ -106,21 +106,28 @@ export async function generateResponse( ): Promise { const apiMessages = messagesToPrompt(messages, config.systemPrompt); + console.log("[MeshChat] ========== generateResponse (non-streaming) =========="); console.log("[MeshChat] Generating response", { messageCount: apiMessages.length, modelId: config.modelId, hasAgent: !!config.agentId, + agentId: config.agentId, + meshUrl: config.meshUrl, }); + const startTime = Date.now(); const response = await callDecopilotAPI(config, apiMessages); if (!response.body) { + console.error("[MeshChat] No response body from Decopilot API!"); throw new Error("No response body from Decopilot API"); } + console.log(`[MeshChat] Collecting stream text...`); const text = await collectFullStreamText(response.body); - console.log(`[MeshChat] Response received (${text.length} chars)`); + console.log(`[MeshChat] Response fully collected in ${Date.now() - startTime}ms (${text.length} chars)`); + console.log(`[MeshChat] Response preview: "${text.substring(0, 300)}"`); return text || "Desculpe, não consegui gerar uma resposta."; } @@ -139,17 +146,26 @@ export async function generateResponseWithStreaming( ): Promise { const apiMessages = messagesToPrompt(messages, config.systemPrompt); + console.log("[MeshChat] ========== generateResponseWithStreaming =========="); console.log("[MeshChat] Generating response (streaming)", { messageCount: apiMessages.length, modelId: config.modelId, hasAgent: !!config.agentId, + agentId: config.agentId, + meshUrl: config.meshUrl, }); + const startTime = Date.now(); const response = await callDecopilotAPI(config, apiMessages); if (!response.body) { + console.error("[MeshChat] No response body from Decopilot API (streaming)!"); throw new Error("No response body from Decopilot API"); } - return processStreamWithCallback(response.body, onStream); + console.log(`[MeshChat] Starting stream processing...`); + const result = await processStreamWithCallback(response.body, onStream); + console.log(`[MeshChat] Streaming completed in ${Date.now() - startTime}ms (${result.length} chars)`); + + return result; } diff --git a/slack-mcp/server/lib/llm.ts b/slack-mcp/server/lib/llm.ts index 4e38732d..1bae1349 100644 --- a/slack-mcp/server/lib/llm.ts +++ b/slack-mcp/server/lib/llm.ts @@ -49,7 +49,24 @@ export async function generateLLMResponse( messages: Message[], config: MeshChatConfig, ): Promise { - return generateResponse(config, toSharedMessages(messages)); + console.log(`[LLM] ========== generateLLMResponse (non-streaming) ==========`); + console.log(`[LLM] Config: meshUrl=${config.meshUrl}, model=${config.modelId}, agentId=${config.agentId ?? "none"}, org=${config.organizationId}`); + console.log(`[LLM] Messages: ${messages.length} total`); + messages.forEach((m, i) => { + console.log(`[LLM] [${i}] role=${m.role}, content length=${m.content.length}, images=${m.images?.length ?? 0}`); + }); + console.log(`[LLM] Has system prompt: ${!!config.systemPrompt}, system prompt length: ${config.systemPrompt?.length ?? 0}`); + + const startTime = Date.now(); + try { + const result = await generateResponse(config, toSharedMessages(messages)); + console.log(`[LLM] Response received in ${Date.now() - startTime}ms. Length: ${result.length} chars`); + console.log(`[LLM] Response preview: "${result.substring(0, 300)}"`); + return result; + } catch (error) { + console.error(`[LLM] generateLLMResponse FAILED after ${Date.now() - startTime}ms:`, error); + throw error; + } } /** @@ -62,9 +79,33 @@ export async function generateLLMResponseWithStreaming( config: MeshChatConfig, onStream: StreamCallback, ): Promise { - return generateResponseWithStreaming( - config, - toSharedMessages(messages), - onStream, - ); + console.log(`[LLM] ========== generateLLMResponseWithStreaming ==========`); + console.log(`[LLM] Config: meshUrl=${config.meshUrl}, model=${config.modelId}, agentId=${config.agentId ?? "none"}, org=${config.organizationId}`); + console.log(`[LLM] Messages: ${messages.length} total`); + messages.forEach((m, i) => { + console.log(`[LLM] [${i}] role=${m.role}, content length=${m.content.length}, images=${m.images?.length ?? 0}`); + }); + console.log(`[LLM] Has system prompt: ${!!config.systemPrompt}, system prompt length: ${config.systemPrompt?.length ?? 0}`); + + const startTime = Date.now(); + let chunkCount = 0; + try { + const result = await generateResponseWithStreaming( + config, + toSharedMessages(messages), + (text, isComplete) => { + chunkCount++; + if (isComplete) { + console.log(`[LLM] Streaming complete. Total chunks: ${chunkCount}, final length: ${text.length} chars, time: ${Date.now() - startTime}ms`); + console.log(`[LLM] Streaming response preview: "${text.substring(0, 300)}"`); + } + onStream(text, isComplete); + }, + ); + console.log(`[LLM] generateLLMResponseWithStreaming finished in ${Date.now() - startTime}ms`); + return result; + } catch (error) { + console.error(`[LLM] generateLLMResponseWithStreaming FAILED after ${Date.now() - startTime}ms (chunks received: ${chunkCount}):`, error); + throw error; + } } diff --git a/slack-mcp/server/lib/slack-client.ts b/slack-mcp/server/lib/slack-client.ts index 6d90ec31..c28a28bd 100644 --- a/slack-mcp/server/lib/slack-client.ts +++ b/slack-mcp/server/lib/slack-client.ts @@ -227,11 +227,15 @@ export async function sendMessage( options: SendMessageOptions, ): Promise { if (!webClient) { - console.error("[Slack] Client not initialized"); + console.error("[Slack] Client not initialized - cannot send message"); return null; } + console.log(`[Slack] sendMessage: channel=${options.channel}, threadTs=${options.threadTs ?? "none"}, text length=${options.text?.length ?? 0}, has blocks=${!!options.blocks}`); + console.log(`[Slack] sendMessage text preview: "${(options.text ?? "").substring(0, 150)}"`); + try { + const startTime = Date.now(); const result = await webClient.chat.postMessage({ channel: options.channel, text: options.text, @@ -242,9 +246,10 @@ export async function sendMessage( mrkdwn: options.mrkdwn ?? true, }); + console.log(`[Slack] sendMessage SUCCESS in ${Date.now() - startTime}ms. Response TS: ${result.ts}, OK: ${result.ok}`); return result; } catch (error) { - console.error("[Slack] Failed to send message:", error); + console.error("[Slack] sendMessage FAILED:", error); throw error; } } @@ -277,16 +282,20 @@ export async function updateMessage( ): Promise { if (!webClient) return false; + console.log(`[Slack] updateMessage: channel=${channel}, ts=${ts}, text length=${text.length}, has blocks=${!!blocks}`); + try { + const startTime = Date.now(); await webClient.chat.update({ channel, ts, text, blocks, }); + console.log(`[Slack] updateMessage SUCCESS in ${Date.now() - startTime}ms`); return true; } catch (error) { - console.error("[Slack] Failed to update message:", error); + console.error("[Slack] updateMessage FAILED:", error); return false; } } @@ -326,7 +335,10 @@ export async function getChannelHistory( ): Promise { if (!webClient) return []; + console.log(`[Slack] getChannelHistory: channel=${channel}, limit=${options.limit ?? 100}, latest=${options.latest ?? "none"}`); + try { + const startTime = Date.now(); const result = await webClient.conversations.history({ channel, limit: options.limit ?? 100, @@ -335,9 +347,11 @@ export async function getChannelHistory( inclusive: options.inclusive, }); - return (result.messages as SlackMessage[]) ?? []; + const messages = (result.messages as SlackMessage[]) ?? []; + console.log(`[Slack] getChannelHistory: ${messages.length} messages fetched in ${Date.now() - startTime}ms`); + return messages; } catch (error) { - console.error("[Slack] Failed to get channel history:", error); + console.error("[Slack] getChannelHistory FAILED:", error); return []; } } @@ -352,16 +366,21 @@ export async function getThreadReplies( ): Promise { if (!webClient) return []; + console.log(`[Slack] getThreadReplies: channel=${channel}, threadTs=${threadTs}, limit=${limit}`); + try { + const startTime = Date.now(); const result = await webClient.conversations.replies({ channel, ts: threadTs, limit, }); - return (result.messages as SlackMessage[]) ?? []; + const messages = (result.messages as SlackMessage[]) ?? []; + console.log(`[Slack] getThreadReplies: ${messages.length} messages fetched in ${Date.now() - startTime}ms`); + return messages; } catch (error) { - console.error("[Slack] Failed to get thread replies:", error); + console.error("[Slack] getThreadReplies FAILED:", error); return []; } } diff --git a/slack-mcp/server/router.ts b/slack-mcp/server/router.ts index 4efd60ee..92a067e3 100644 --- a/slack-mcp/server/router.ts +++ b/slack-mcp/server/router.ts @@ -342,7 +342,19 @@ async function processConnectionEventAsync( connectionConfig: SlackConnectionConfig, traceId: string, ): Promise { - if (!payload.event) return; + if (!payload.event) { + console.log(`[Router] [${traceId}] No event in payload, skipping`); + return; + } + + console.log(`[Router] [${traceId}] ========== START processConnectionEventAsync ==========`); + console.log(`[Router] [${traceId}] Event type: ${payload.event.type}, subtype: ${payload.event.subtype ?? "none"}`); + console.log(`[Router] [${traceId}] Channel: ${payload.event.channel}, User: ${payload.event.user}, TS: ${payload.event.ts}`); + console.log(`[Router] [${traceId}] Thread TS: ${payload.event.thread_ts ?? "none"}`); + console.log(`[Router] [${traceId}] Text: ${(payload.event.text ?? "").substring(0, 200)}`); + console.log(`[Router] [${traceId}] Connection: ${connectionConfig.connectionId}, Team: ${connectionConfig.teamId}`); + console.log(`[Router] [${traceId}] Has files: ${!!payload.event.files}, File count: ${payload.event.files?.length ?? 0}`); + console.log(`[Router] [${traceId}] Bot user ID from config: ${connectionConfig.botUserId ?? "not set"}`); // IMPORTANT: Initialize Slack client with this connection's token // Each connection has its own Slack workspace credentials @@ -351,18 +363,22 @@ async function processConnectionEventAsync( // Configure LLM with this connection's settings (modelProviderId is optional) // Falls back to FALLBACK_MODEL_ID when modelId is not configured if (connectionConfig.meshToken) { + const modelId = connectionConfig.modelId ?? FALLBACK_MODEL_ID; + console.log(`[Router] [${traceId}] Configuring LLM: model=${modelId}, agentId=${connectionConfig.agentId ?? "none"}, meshUrl=${connectionConfig.meshUrl}`); + console.log(`[Router] [${traceId}] Has system prompt: ${!!connectionConfig.systemPrompt}, prompt length: ${connectionConfig.systemPrompt?.length ?? 0}`); configureLLM({ meshUrl: connectionConfig.meshUrl, organizationId: connectionConfig.organizationId, token: connectionConfig.meshToken, modelProviderId: connectionConfig.modelProviderId, - modelId: connectionConfig.modelId ?? FALLBACK_MODEL_ID, + modelId, agentId: connectionConfig.agentId, systemPrompt: connectionConfig.systemPrompt, }); } else { // Clear LLM config to prevent cross-tenant configuration leakage clearLLMConfig(); + console.log(`[Router] [${traceId}] LLM NOT configured - missing meshToken for connection ${connectionConfig.connectionId}`); logger.warn("LLM not configured - missing meshToken", { connectionId: connectionConfig.connectionId, trace_id: traceId, @@ -375,6 +391,7 @@ async function processConnectionEventAsync( const enableStreaming = showOnlyFinal ? false : (connectionConfig.responseConfig?.enableStreaming ?? true); + console.log(`[Router] [${traceId}] Response config: showOnlyFinal=${showOnlyFinal}, streaming=${enableStreaming}, showThinking=${connectionConfig.responseConfig?.showThinkingMessage ?? "default(true)"}`); configureStreaming(enableStreaming); const event = payload.event; @@ -392,19 +409,26 @@ async function processConnectionEventAsync( if (botUserId) { cleanText = removeBotMention(cleanText, botUserId); } + console.log(`[Router] [${traceId}] Event is app_mention, will process. Clean text: "${cleanText.substring(0, 100)}"`); } else if (eventType === "message") { const isDM = event.channel?.startsWith("D"); if (isDM) { shouldProcess = true; + console.log(`[Router] [${traceId}] Event is DM message, will process`); } else if (botUserId && isBotMentioned(event.text ?? "", botUserId)) { shouldProcess = true; cleanText = removeBotMention(event.text ?? "", botUserId); + console.log(`[Router] [${traceId}] Event is channel message with bot mention, will process`); + } else { + console.log(`[Router] [${traceId}] Event is channel message without bot mention, checking if should skip. botUserId=${botUserId}, text contains mention: ${botUserId ? (event.text ?? "").includes(`<@${botUserId}>`) : "N/A"}`); } } else { shouldProcess = true; + console.log(`[Router] [${traceId}] Event type ${eventType} will be processed`); } if (shouldProcess) { + console.log(`[Router] [${traceId}] Dispatching to handleSlackEvent...`); // Convert to team config format for compatibility with handleSlackEvent const teamConfig: SlackTeamConfig = { teamId: connectionConfig.teamId ?? payload.team_id ?? "", @@ -430,6 +454,10 @@ async function processConnectionEventAsync( }, teamConfig, ); + console.log(`[Router] [${traceId}] ========== END processConnectionEventAsync (success) ==========`); + } else { + console.log(`[Router] [${traceId}] Event NOT processed (shouldProcess=false)`); + console.log(`[Router] [${traceId}] ========== END processConnectionEventAsync (skipped) ==========`); } } diff --git a/slack-mcp/server/slack/handlers/context-builder.ts b/slack-mcp/server/slack/handlers/context-builder.ts index c1cdacce..49e6a697 100644 --- a/slack-mcp/server/slack/handlers/context-builder.ts +++ b/slack-mcp/server/slack/handlers/context-builder.ts @@ -104,28 +104,40 @@ export async function buildContextMessages( currentTs: string, limit: number = contextConfig.maxMessagesToFetch, ): Promise { + console.log(`[ContextBuilder] ========== buildContextMessages ==========`); + console.log(`[ContextBuilder] Channel: ${channel}, threadTs: ${threadTs ?? "none"}, currentTs: ${currentTs}, limit: ${limit}`); + console.log(`[ContextBuilder] Config: maxFetch=${contextConfig.maxMessagesToFetch}, maxBeforeSummary=${contextConfig.maxMessagesBeforeSummary}, recentToKeep=${contextConfig.recentMessagesToKeep}`); + console.log(`[ContextBuilder] Bot user ID: ${globalBotUserId ?? "not set"}`); + const messages: ContextMessage[] = []; // Fetch messages from Slack let allMessages: Array<{ user?: string; text: string; ts: string }> = []; + const fetchStartTime = Date.now(); if (threadTs) { + console.log(`[ContextBuilder] Fetching thread replies for ${threadTs}...`); const threadMessages = await getThreadReplies( channel, threadTs, contextConfig.maxMessagesToFetch, ); + console.log(`[ContextBuilder] Thread replies fetched: ${threadMessages.length} raw messages in ${Date.now() - fetchStartTime}ms`); allMessages = threadMessages .filter((msg) => msg.ts !== currentTs && msg.text) .sort((a, b) => Number.parseFloat(a.ts) - Number.parseFloat(b.ts)); + console.log(`[ContextBuilder] After filtering: ${allMessages.length} messages (excluded currentTs=${currentTs})`); } else { + console.log(`[ContextBuilder] Fetching channel history...`); const channelMessages = await getChannelHistory(channel, { limit: contextConfig.maxMessagesToFetch, latest: currentTs, }); + console.log(`[ContextBuilder] Channel history fetched: ${channelMessages.length} raw messages in ${Date.now() - fetchStartTime}ms`); allMessages = channelMessages .filter((msg) => msg.ts !== currentTs && msg.text) .sort((a, b) => Number.parseFloat(a.ts) - Number.parseFloat(b.ts)); + console.log(`[ContextBuilder] After filtering: ${allMessages.length} messages`); } // Convert to role-based messages @@ -136,6 +148,11 @@ export async function buildContextMessages( content: msg.text, })); + console.log(`[ContextBuilder] Role messages: ${roleMessages.length} (user: ${roleMessages.filter(m => m.role === "user").length}, assistant: ${roleMessages.filter(m => m.role === "assistant").length})`); + roleMessages.forEach((msg, i) => { + console.log(`[ContextBuilder] [${i}] ${msg.role}: "${msg.content.substring(0, 80)}${msg.content.length > 80 ? "..." : ""}"`); + }); + // Check if we need to summarize if (roleMessages.length > contextConfig.maxMessagesBeforeSummary) { const recentCount = contextConfig.recentMessagesToKeep; @@ -154,8 +171,10 @@ export async function buildContextMessages( ); } else { messages.push(...roleMessages.slice(-limit)); + console.log(`[ContextBuilder] Using ${messages.length} messages (no summary needed)`); } + console.log(`[ContextBuilder] Final context: ${messages.length} messages`); return messages; } diff --git a/slack-mcp/server/slack/handlers/eventHandler.ts b/slack-mcp/server/slack/handlers/eventHandler.ts index aaceeda5..4dff512c 100644 --- a/slack-mcp/server/slack/handlers/eventHandler.ts +++ b/slack-mcp/server/slack/handlers/eventHandler.ts @@ -470,12 +470,14 @@ export async function handleSlackEvent( ): Promise { const { type, payload } = context; - console.log(`[EventHandler] Processing: ${type}`, { - channel: payload.channel, - user: payload.user, - ts: payload.ts, - teamId: teamConfig.teamId, - }); + console.log(`[EventHandler] ========== handleSlackEvent ==========`); + console.log(`[EventHandler] Event type: ${type}`); + console.log(`[EventHandler] Channel: ${payload.channel}, User: ${payload.user}, TS: ${payload.ts}`); + console.log(`[EventHandler] Thread TS: ${payload.thread_ts ?? "none"}, Team: ${teamConfig.teamId}`); + console.log(`[EventHandler] Text preview: "${(payload.text ?? "").substring(0, 150)}"`); + console.log(`[EventHandler] Has files: ${!!payload.files}, Channel type: ${payload.channel_type ?? "unknown"}`); + console.log(`[EventHandler] Bot user ID (global): ${globalBotUserId ?? "not set"}, Bot user ID (team): ${teamConfig.botUserId ?? "not set"}`); + console.log(`[EventHandler] Response config: showOnlyFinal=${teamConfig.responseConfig?.showOnlyFinalResponse ?? false}, streaming=${teamConfig.responseConfig?.enableStreaming ?? "default"}, thinking=${teamConfig.responseConfig?.showThinkingMessage ?? "default"}`); switch (type) { case "app_mention": @@ -506,7 +508,11 @@ async function handleAppMention( teamConfig: SlackTeamConfig, ): Promise { const { channel, user, text, ts, thread_ts, files } = event; - console.log(`[EventHandler] App mention from ${user} in ${channel}`); + console.log(`[EventHandler] ========== handleAppMention ==========`); + console.log(`[EventHandler] From user: ${user}, Channel: ${channel}, TS: ${ts}`); + console.log(`[EventHandler] Thread TS: ${thread_ts ?? "new thread"}`); + console.log(`[EventHandler] Full text: "${text}"`); + console.log(`[EventHandler] Files attached: ${files?.length ?? 0}`); // Check if we're in "show only final response" mode const showOnlyFinal = @@ -581,6 +587,7 @@ async function handleAppMention( } // Build messages for LLM + console.log(`[EventHandler] Building LLM messages for app_mention. Full text length: ${fullText.length}, media count: ${mediaForLLM.length}`); const messages = await buildLLMMessages( channel, fullText, @@ -589,6 +596,10 @@ async function handleAppMention( mediaForLLM, true, // Clean bot mention ); + console.log(`[EventHandler] LLM messages built: ${messages.length} messages total`); + messages.forEach((msg, i) => { + console.log(`[EventHandler] Message[${i}]: role=${msg.role}, content length=${msg.content.length}, has images=${!!msg.images}, content preview="${msg.content.substring(0, 100)}"`); + }); // Check if LLM is configured if (!isLLMConfigured()) { @@ -604,13 +615,17 @@ async function handleAppMention( } // Call LLM + console.log(`[EventHandler] Calling LLM for app_mention. Channel: ${channel}, replyTo: ${replyTo}, thinkingTs: ${thinkingMsg?.ts ?? "none"}`); + const llmStartTime = Date.now(); try { await handleLLMCall(messages, { channel, replyTo, thinkingMessageTs: thinkingMsg?.ts, }); + console.log(`[EventHandler] LLM call completed for app_mention in ${Date.now() - llmStartTime}ms`); } catch (error) { + console.error(`[EventHandler] LLM call FAILED for app_mention after ${Date.now() - llmStartTime}ms:`, error); logger.error("App mention LLM error", { channel, userId: user, @@ -630,21 +645,29 @@ async function handleMessage( const isDM = channel_type === "im" || channel?.startsWith("D"); const botUserId = globalBotUserId ?? teamConfig.botUserId; + console.log(`[EventHandler] ========== handleMessage ==========`); + console.log(`[EventHandler] From user: ${user}, Channel: ${channel}, TS: ${ts}`); + console.log(`[EventHandler] isDM: ${isDM}, channel_type: ${channel_type ?? "unknown"}, thread_ts: ${thread_ts ?? "none"}`); + console.log(`[EventHandler] Text: "${(text ?? "").substring(0, 200)}"`); + console.log(`[EventHandler] Files: ${files?.length ?? 0}, botUserId: ${botUserId ?? "not set"}`); + // Skip messages with bot mention in channels (handled by app_mention) if (!isDM && botUserId && text?.includes(`<@${botUserId}>`)) { - console.log("[EventHandler] Skipping - will be handled by app_mention"); + console.log("[EventHandler] Skipping message - will be handled by app_mention event instead"); return; } // For threads, check if bot participated if (thread_ts && !isDM) { + console.log(`[EventHandler] Checking bot participation in thread ${thread_ts}...`); const botParticipated = await botParticipatedInThread( channel, thread_ts, botUserId ?? null, ); + console.log(`[EventHandler] Bot participated in thread: ${botParticipated}`); if (!botParticipated) { - console.log(`[EventHandler] Ignoring thread reply - bot not in thread`); + console.log(`[EventHandler] Ignoring thread reply - bot not in thread ${thread_ts}`); return; } } @@ -704,6 +727,7 @@ async function handleMessage( }; if (isDM) { + console.log(`[EventHandler] Routing to handleDirectMessage. Text length: ${fullText.length}, media: ${mediaForLLM.length}`); await handleDirectMessage( channel, user, @@ -714,6 +738,7 @@ async function handleMessage( teamConfig, ); } else if (thread_ts) { + console.log(`[EventHandler] Routing to handleThreadReply. Thread: ${thread_ts}, text length: ${fullText.length}, media: ${mediaForLLM.length}`); await handleThreadReply( channel, user, @@ -745,7 +770,10 @@ async function handleDirectMessage( meshConfig: MeshConfig, teamConfig: SlackTeamConfig, ): Promise { - console.log(`[EventHandler] DM from ${user}`); + console.log(`[EventHandler] ========== handleDirectMessage ==========`); + console.log(`[EventHandler] DM from user: ${user}, Channel: ${channel}, TS: ${ts}`); + console.log(`[EventHandler] Text: "${text.substring(0, 200)}"`); + console.log(`[EventHandler] Media count: ${media.length}`); // Check if we're in "show only final response" mode const showOnlyFinal = @@ -753,6 +781,7 @@ async function handleDirectMessage( // Add eyes reaction immediately for quick feedback (unless in silent mode) if (!showOnlyFinal) { + console.log(`[EventHandler] Adding eyes reaction to ${ts}`); await addReaction(channel, ts, "eyes"); } @@ -760,14 +789,21 @@ async function handleDirectMessage( const showThinking = showOnlyFinal ? false : (teamConfig.responseConfig?.showThinkingMessage ?? true); + console.log(`[EventHandler] Sending thinking message: ${showThinking}`); const thinkingMsg = showThinking ? await sendThinkingMessage(channel) : null; + console.log(`[EventHandler] Thinking message TS: ${thinkingMsg?.ts ?? "not sent"}`); // Remove eyes reaction when we start processing (unless in silent mode) if (!showOnlyFinal) { await removeReaction(channel, ts, "eyes"); } + console.log(`[EventHandler] Building LLM messages for DM...`); const messages = await buildLLMMessages(channel, text, ts, undefined, media); + console.log(`[EventHandler] LLM messages built: ${messages.length} messages`); + messages.forEach((msg, i) => { + console.log(`[EventHandler] Message[${i}]: role=${msg.role}, content length=${msg.content.length}, has images=${!!msg.images}`); + }); if (!isLLMConfigured()) { const warningMsg = @@ -787,12 +823,16 @@ async function handleDirectMessage( return; } + console.log(`[EventHandler] Calling LLM for DM. Channel: ${channel}, thinkingTs: ${thinkingMsg?.ts ?? "none"}`); + const dmLlmStartTime = Date.now(); try { await handleLLMCall(messages, { channel, thinkingMessageTs: thinkingMsg?.ts, }); + console.log(`[EventHandler] LLM call completed for DM in ${Date.now() - dmLlmStartTime}ms`); } catch (error) { + console.error(`[EventHandler] LLM call FAILED for DM after ${Date.now() - dmLlmStartTime}ms:`, error); logger.error("Direct message LLM error", { channel, userId: user, @@ -822,7 +862,10 @@ async function handleThreadReply( meshConfig: MeshConfig, teamConfig: SlackTeamConfig, ): Promise { - console.log(`[EventHandler] Thread reply from ${user}`); + console.log(`[EventHandler] ========== handleThreadReply ==========`); + console.log(`[EventHandler] Thread reply from user: ${user}, Channel: ${channel}, TS: ${ts}, ThreadTS: ${threadTs}`); + console.log(`[EventHandler] Text: "${text.substring(0, 200)}"`); + console.log(`[EventHandler] Media count: ${media.length}`); // Check if we're in "show only final response" mode const showOnlyFinal = @@ -830,6 +873,7 @@ async function handleThreadReply( // Add eyes reaction immediately for quick feedback (unless in silent mode) if (!showOnlyFinal) { + console.log(`[EventHandler] Adding eyes reaction to ${ts}`); await addReaction(channel, ts, "eyes"); } @@ -837,16 +881,23 @@ async function handleThreadReply( const showThinking = showOnlyFinal ? false : (teamConfig.responseConfig?.showThinkingMessage ?? true); + console.log(`[EventHandler] Sending thinking message in thread ${threadTs}: ${showThinking}`); const thinkingMsg = showThinking ? await sendThinkingMessage(channel, threadTs) : null; + console.log(`[EventHandler] Thinking message TS: ${thinkingMsg?.ts ?? "not sent"}`); // Remove eyes reaction when we start processing (unless in silent mode) if (!showOnlyFinal) { await removeReaction(channel, ts, "eyes"); } + console.log(`[EventHandler] Building LLM messages for thread reply...`); const messages = await buildLLMMessages(channel, text, ts, threadTs, media); + console.log(`[EventHandler] LLM messages built: ${messages.length} messages`); + messages.forEach((msg, i) => { + console.log(`[EventHandler] Message[${i}]: role=${msg.role}, content length=${msg.content.length}, has images=${!!msg.images}`); + }); if (!isLLMConfigured()) { const warningMsg = @@ -866,13 +917,17 @@ async function handleThreadReply( return; } + console.log(`[EventHandler] Calling LLM for thread reply. Channel: ${channel}, threadTs: ${threadTs}, thinkingTs: ${thinkingMsg?.ts ?? "none"}`); + const threadLlmStartTime = Date.now(); try { await handleLLMCall(messages, { channel, replyTo: threadTs, thinkingMessageTs: thinkingMsg?.ts, }); + console.log(`[EventHandler] LLM call completed for thread reply in ${Date.now() - threadLlmStartTime}ms`); } catch (error) { + console.error(`[EventHandler] LLM call FAILED for thread reply after ${Date.now() - threadLlmStartTime}ms:`, error); logger.error("Thread reply LLM error", { channel, userId: user, diff --git a/slack-mcp/server/slack/handlers/llm-handler.ts b/slack-mcp/server/slack/handlers/llm-handler.ts index 0689fd92..8b9d162e 100644 --- a/slack-mcp/server/slack/handlers/llm-handler.ts +++ b/slack-mcp/server/slack/handlers/llm-handler.ts @@ -134,13 +134,19 @@ export async function callLLMWithStreaming( const { channel, thinkingMessageTs, useBlocks = true } = options; + console.log(`[LLMHandler] ========== callLLMWithStreaming ==========`); + console.log(`[LLMHandler] Channel: ${channel}, thinkingTs: ${thinkingMessageTs ?? "none"}`); + if (!thinkingMessageTs) { + console.log(`[LLMHandler] No thinking message, falling back to non-streaming`); return callLLMWithoutStreaming(messages, options); } const animation = startThinkingAnimation(channel, thinkingMessageTs); try { + console.log(`[LLMHandler] Starting streaming LLM call...`); + const streamStartTime = Date.now(); const response = await generateLLMResponseWithStreaming( messages, globalLLMConfig, @@ -148,19 +154,24 @@ export async function callLLMWithStreaming( if (!isComplete) return; animation.stop(); + console.log(`[LLMHandler] Streaming complete. Response length: ${text.length} chars, time: ${Date.now() - streamStartTime}ms`); + console.log(`[LLMHandler] Response preview: "${text.substring(0, 200)}"`); const formattedText = formatForSlack(text); + console.log(`[LLMHandler] Formatted text length: ${formattedText.length}, using blocks: ${useBlocks && text.length > 500}`); const blocks = useBlocks && text.length > 500 ? buildResponseBlocks(text, { addFeedbackButtons: false }) : undefined; + console.log(`[LLMHandler] Updating thinking message ${thinkingMessageTs} with final response`); await updateThinkingMessage( channel, thinkingMessageTs, formattedText, blocks, ); + console.log(`[LLMHandler] Thinking message updated successfully`); }, ); @@ -183,24 +194,37 @@ export async function callLLMWithoutStreaming( const { channel, replyTo, thinkingMessageTs, useBlocks = true } = options; + console.log(`[LLMHandler] ========== callLLMWithoutStreaming ==========`); + console.log(`[LLMHandler] Channel: ${channel}, replyTo: ${replyTo ?? "none"}, thinkingTs: ${thinkingMessageTs ?? "none"}`); + const nonStreamStartTime = Date.now(); + console.log(`[LLMHandler] Starting non-streaming LLM call...`); const response = await generateLLMResponse(messages, globalLLMConfig); + console.log(`[LLMHandler] Non-streaming LLM response received in ${Date.now() - nonStreamStartTime}ms. Length: ${response.length} chars`); + console.log(`[LLMHandler] Response preview: "${response.substring(0, 200)}"`); const formattedResponse = formatForSlack(response); + console.log(`[LLMHandler] Formatted response length: ${formattedResponse.length}`); const blocks = useBlocks && response.length > 500 ? buildResponseBlocks(response, { addFeedbackButtons: false }) : undefined; if (thinkingMessageTs) { + console.log(`[LLMHandler] Updating thinking message ${thinkingMessageTs} with response`); await updateThinkingMessage( channel, thinkingMessageTs, formattedResponse, blocks, ); + console.log(`[LLMHandler] Thinking message updated`); } else if (replyTo) { + console.log(`[LLMHandler] Replying in thread ${replyTo}`); await replyInThread(channel, replyTo, formattedResponse, blocks); + console.log(`[LLMHandler] Thread reply sent`); } else { + console.log(`[LLMHandler] Sending message to channel ${channel}`); await sendMessage({ channel, text: formattedResponse, blocks }); + console.log(`[LLMHandler] Message sent`); } return response; @@ -216,15 +240,24 @@ export async function handleLLMCall( ): Promise { const { channel, replyTo, thinkingMessageTs } = options; + console.log(`[LLMHandler] ========== handleLLMCall ==========`); + console.log(`[LLMHandler] Channel: ${channel}, replyTo: ${replyTo ?? "none"}, thinkingTs: ${thinkingMessageTs ?? "none"}`); + console.log(`[LLMHandler] Messages count: ${messages.length}, streaming enabled: ${streamingEnabled}`); + console.log(`[LLMHandler] LLM config: model=${globalLLMConfig?.modelId ?? "NOT SET"}, agentId=${globalLLMConfig?.agentId ?? "none"}, meshUrl=${globalLLMConfig?.meshUrl ?? "NOT SET"}`); + console.log(`[LLMHandler] Has system prompt: ${!!globalLLMConfig?.systemPrompt}`); + try { // Use streaming only if enabled AND we have a thinking message to update const useStreaming = streamingEnabled && !!thinkingMessageTs; + console.log(`[LLMHandler] Using streaming: ${useStreaming} (streamingEnabled=${streamingEnabled}, hasThinkingMsg=${!!thinkingMessageTs})`); + const startTime = Date.now(); if (useStreaming) { await callLLMWithStreaming(messages, options); } else { await callLLMWithoutStreaming(messages, options); } + console.log(`[LLMHandler] LLM call completed successfully in ${Date.now() - startTime}ms`); } catch (error) { logger.error("LLM response failed", { channel, diff --git a/slack-mcp/server/webhook.ts b/slack-mcp/server/webhook.ts index 8b1fb520..fd3dea13 100644 --- a/slack-mcp/server/webhook.ts +++ b/slack-mcp/server/webhook.ts @@ -138,10 +138,16 @@ export function shouldIgnoreEvent( const event = payload.event; // Ignore bot messages - if (event.bot_id) return true; + if (event.bot_id) { + console.log(`[Webhook] Ignoring event: bot_id=${event.bot_id}, type=${event.type}, user=${event.user}`); + return true; + } // Ignore messages from ourselves - if (botUserId && event.user === botUserId) return true; + if (botUserId && event.user === botUserId) { + console.log(`[Webhook] Ignoring event: message from ourselves (botUserId=${botUserId})`); + return true; + } // Ignore message subtypes that aren't actual user messages const ignoredSubtypes = [ @@ -165,9 +171,11 @@ export function shouldIgnoreEvent( ]; if (event.subtype && ignoredSubtypes.includes(event.subtype)) { + console.log(`[Webhook] Ignoring event: subtype=${event.subtype}, type=${event.type}`); return true; } + console.log(`[Webhook] Event NOT ignored: type=${event.type}, subtype=${event.subtype ?? "none"}, user=${event.user}, bot_id=${event.bot_id ?? "none"}`); return false; } From 4424c72da8d2269598e19cf06d8d7964be58e2dc Mon Sep 17 00:00:00 2001 From: gimenes Date: Mon, 23 Mar 2026 10:56:19 -0300 Subject: [PATCH 2/5] style: fix formatting with oxfmt Co-Authored-By: Claude Opus 4.6 (1M context) --- .../server/tools/embeddings/generate.ts | 9 +- .../server/tools/embeddings/list-models.ts | 21 ++- openrouter/server/utils.ts | 161 +++++++++--------- shared/mesh-chat/client.ts | 36 +++- shared/mesh-chat/generate.ts | 20 ++- slack-mcp/server/lib/llm.ts | 54 ++++-- slack-mcp/server/lib/slack-client.ts | 32 +++- slack-mcp/server/router.ts | 76 ++++++--- .../server/slack/handlers/context-builder.ts | 36 +++- .../server/slack/handlers/eventHandler.ts | 143 ++++++++++++---- .../server/slack/handlers/llm-handler.ts | 64 +++++-- slack-mcp/server/types/env.ts | 2 +- slack-mcp/server/webhook.ts | 16 +- 13 files changed, 454 insertions(+), 216 deletions(-) diff --git a/openrouter/server/tools/embeddings/generate.ts b/openrouter/server/tools/embeddings/generate.ts index 7bf385ee..be661f0e 100644 --- a/openrouter/server/tools/embeddings/generate.ts +++ b/openrouter/server/tools/embeddings/generate.ts @@ -50,19 +50,18 @@ export const createGenerateEmbeddingsTool = (env: Env) => const { input, model, encoding_format, dimensions } = context; const sdk = new OpenRouter({ apiKey: getOpenRouterApiKey(env) }); - console.log({input, model, encoding_format, dimensions}); + console.log({ input, model, encoding_format, dimensions }); const result = await sdk.embeddings.generate({ input: input, - model: model, + model: model, encodingFormat: encoding_format, dimensions: dimensions, }); - console.log({result}); - + console.log({ result }); return { - data: result + data: result, }; }, }); diff --git a/openrouter/server/tools/embeddings/list-models.ts b/openrouter/server/tools/embeddings/list-models.ts index 31578c99..2164382b 100644 --- a/openrouter/server/tools/embeddings/list-models.ts +++ b/openrouter/server/tools/embeddings/list-models.ts @@ -8,10 +8,12 @@ import { getOpenRouterApiKey } from "server/lib/env.ts"; import { z } from "zod"; import { OpenRouterClient } from "../../lib/openrouter-client.ts"; import type { Env } from "../../main.ts"; - import { createCollectionBindings } from "@decocms/bindings/collections"; +import { createCollectionBindings } from "@decocms/bindings/collections"; import { OpenRouter } from "@openrouter/sdk"; - const EMBEDDING_MODELS_BINDING = createCollectionBindings("EMBEDDING_MODELS", z.object({ +const EMBEDDING_MODELS_BINDING = createCollectionBindings( + "EMBEDDING_MODELS", + z.object({ id: z.string(), name: z.string(), description: z.string().optional(), @@ -20,14 +22,18 @@ import { OpenRouter } from "@openrouter/sdk"; prompt: z.string(), completion: z.string(), }), - }), { readOnly: true }); + }), + { readOnly: true }, +); const LIST_BINDING = EMBEDDING_MODELS_BINDING.find( (b) => b.name === "COLLECTION_EMBEDDING_MODELS_LIST", ); if (!LIST_BINDING?.inputSchema || !LIST_BINDING?.outputSchema) { - throw new Error("COLLECTION_EMBEDDING_MODELS_LIST binding not found or missing schemas"); + throw new Error( + "COLLECTION_EMBEDDING_MODELS_LIST binding not found or missing schemas", + ); } export const createListEmbeddingModelsTool = (env: Env) => @@ -39,11 +45,10 @@ export const createListEmbeddingModelsTool = (env: Env) => "Use this to discover which embedding models are available before generating embeddings." + "Prefer to use a search", inputSchema: LIST_BINDING.inputSchema, -outputSchema: z.object({ - items: z.unknown(), -}), + outputSchema: z.object({ + items: z.unknown(), + }), execute: async () => { - const sdk = new OpenRouter({ apiKey: getOpenRouterApiKey(env) }); const models = (await sdk.embeddings.listModels()).data; diff --git a/openrouter/server/utils.ts b/openrouter/server/utils.ts index 907ac2bb..fca2403d 100644 --- a/openrouter/server/utils.ts +++ b/openrouter/server/utils.ts @@ -2,105 +2,98 @@ import { BaseCollectionEntity } from "@decocms/bindings/collections"; export const OPENROUTER_PROVIDER = "openrouter" as const; type ExtendedCollectionEntity = BaseCollectionEntity & { - [key: string]: unknown; + [key: string]: unknown; }; export function applyWhereFilter( - items: ExtendedCollectionEntity[], - where: Record, + items: ExtendedCollectionEntity[], + where: Record, ): ExtendedCollectionEntity[] { - const whereAny = where as { - operator?: string; - conditions?: Record[]; - field?: string[]; - value?: unknown; - }; + const whereAny = where as { + operator?: string; + conditions?: Record[]; + field?: string[]; + value?: unknown; + }; - if (whereAny.operator === "and" && whereAny.conditions) { - let filtered = items; - for (const condition of whereAny.conditions) { - filtered = applyWhereFilter(filtered, condition); - } - return filtered; + if (whereAny.operator === "and" && whereAny.conditions) { + let filtered = items; + for (const condition of whereAny.conditions) { + filtered = applyWhereFilter(filtered, condition); } + return filtered; + } - if (whereAny.operator === "or" && whereAny.conditions) { - const results = new Set(); - for (const condition of whereAny.conditions) { - applyWhereFilter(items, condition).forEach((m) => { - results.add(m); - }); - } - return Array.from(results); + if (whereAny.operator === "or" && whereAny.conditions) { + const results = new Set(); + for (const condition of whereAny.conditions) { + applyWhereFilter(items, condition).forEach((m) => { + results.add(m); + }); } + return Array.from(results); + } - if (whereAny.field && whereAny.operator && whereAny.value !== undefined) { - const field = whereAny.field[0]; - return items.filter((item: ExtendedCollectionEntity) => { - if (field === "id" || field === "title") { - const modelValue = field === "id" ? item.id : item.name; - if (whereAny.operator === "eq") { - return modelValue === whereAny.value; - } - if ( - whereAny.operator === "like" || - whereAny.operator === "contains" - ) { - return String(modelValue) - .toLowerCase() - .includes(String(whereAny.value).toLowerCase()); - } - if ( - whereAny.operator === "in" && Array.isArray(whereAny.value) - ) { - return whereAny.value.includes(modelValue); - } - } - if (field === "provider") { - // All models are from OpenRouter - if (whereAny.operator === "eq") { - return OPENROUTER_PROVIDER === whereAny.value; - } - if ( - whereAny.operator === "in" && Array.isArray(whereAny.value) - ) { - return whereAny.value.includes(OPENROUTER_PROVIDER); - } - } - return true; - }); - } + if (whereAny.field && whereAny.operator && whereAny.value !== undefined) { + const field = whereAny.field[0]; + return items.filter((item: ExtendedCollectionEntity) => { + if (field === "id" || field === "title") { + const modelValue = field === "id" ? item.id : item.name; + if (whereAny.operator === "eq") { + return modelValue === whereAny.value; + } + if (whereAny.operator === "like" || whereAny.operator === "contains") { + return String(modelValue) + .toLowerCase() + .includes(String(whereAny.value).toLowerCase()); + } + if (whereAny.operator === "in" && Array.isArray(whereAny.value)) { + return whereAny.value.includes(modelValue); + } + } + if (field === "provider") { + // All models are from OpenRouter + if (whereAny.operator === "eq") { + return OPENROUTER_PROVIDER === whereAny.value; + } + if (whereAny.operator === "in" && Array.isArray(whereAny.value)) { + return whereAny.value.includes(OPENROUTER_PROVIDER); + } + } + return true; + }); + } - return items; + return items; } export function applyOrderBy( - models: ExtendedCollectionEntity[], - orderBy: Array<{ field: string[]; direction?: string }>, + models: ExtendedCollectionEntity[], + orderBy: Array<{ field: string[]; direction?: string }>, ): ExtendedCollectionEntity[] { - const sorted = [...models]; - for (const order of orderBy.reverse()) { - const field = order.field[0]; - const direction = order.direction === "desc" ? -1 : 1; + const sorted = [...models]; + for (const order of orderBy.reverse()) { + const field = order.field[0]; + const direction = order.direction === "desc" ? -1 : 1; - sorted.sort((a, b) => { - let aVal: string; - let bVal: string; + sorted.sort((a, b) => { + let aVal: string; + let bVal: string; - if (field === "id") { - aVal = a.id; - bVal = b.id; - } else if (field === "title") { - aVal = a.name as string; - bVal = b.name as string; - } else { - return 0; - } + if (field === "id") { + aVal = a.id; + bVal = b.id; + } else if (field === "title") { + aVal = a.name as string; + bVal = b.name as string; + } else { + return 0; + } - if (aVal < bVal) return -1 * direction; - if (aVal > bVal) return 1 * direction; - return 0; - }); - } - return sorted; + if (aVal < bVal) return -1 * direction; + if (aVal > bVal) return 1 * direction; + return 0; + }); + } + return sorted; } diff --git a/shared/mesh-chat/client.ts b/shared/mesh-chat/client.ts index f8d1e3ba..d9edf9e7 100644 --- a/shared/mesh-chat/client.ts +++ b/shared/mesh-chat/client.ts @@ -73,13 +73,27 @@ export async function callDecopilotAPI( console.log(`[MeshChat] ========== callDecopilotAPI ==========`); console.log(`[MeshChat] URL: ${url}`); - console.log(`[MeshChat] Model: ${modelId}, Provider: ${resolveProvider(modelId)}, Connection: ${modelProviderId ?? "none"}`); + console.log( + `[MeshChat] Model: ${modelId}, Provider: ${resolveProvider(modelId)}, Connection: ${modelProviderId ?? "none"}`, + ); console.log(`[MeshChat] Agent: id=${agentId ?? "none"}, mode=${agentMode}`); - console.log(`[MeshChat] Messages: ${messages.length}, timeout: ${timeoutMs}ms`); - console.log(`[MeshChat] Effective mesh URL: ${effectiveMeshUrl} (original: ${meshUrl})`); + console.log( + `[MeshChat] Messages: ${messages.length}, timeout: ${timeoutMs}ms`, + ); + console.log( + `[MeshChat] Effective mesh URL: ${effectiveMeshUrl} (original: ${meshUrl})`, + ); messages.forEach((m, i) => { - const partsDesc = m.parts.map(p => p.type === "text" ? `text(${(p as any).text?.length ?? 0})` : `file(${(p as any).filename ?? "?"})`).join(", "); - console.log(`[MeshChat] Message[${i}]: role=${m.role}, parts=[${partsDesc}]`); + const partsDesc = m.parts + .map((p) => + p.type === "text" + ? `text(${(p as any).text?.length ?? 0})` + : `file(${(p as any).filename ?? "?"})`, + ) + .join(", "); + console.log( + `[MeshChat] Message[${i}]: role=${m.role}, parts=[${partsDesc}]`, + ); }); const controller = new AbortController(); @@ -99,12 +113,18 @@ export async function callDecopilotAPI( signal: controller.signal, }); - console.log(`[MeshChat] Decopilot API response: status=${response.status}, ok=${response.ok}, time=${Date.now() - fetchStartTime}ms`); - console.log(`[MeshChat] Response headers: content-type=${response.headers.get("content-type")}`); + console.log( + `[MeshChat] Decopilot API response: status=${response.status}, ok=${response.ok}, time=${Date.now() - fetchStartTime}ms`, + ); + console.log( + `[MeshChat] Response headers: content-type=${response.headers.get("content-type")}`, + ); if (!response.ok) { const errorText = await response.text(); - console.error(`[MeshChat] Decopilot API FAILED: status=${response.status}, body=${errorText.substring(0, 500)}`); + console.error( + `[MeshChat] Decopilot API FAILED: status=${response.status}, body=${errorText.substring(0, 500)}`, + ); throw new Error( `Decopilot API call failed (${response.status}): ${errorText}`, ); diff --git a/shared/mesh-chat/generate.ts b/shared/mesh-chat/generate.ts index b291287d..f7cf9ec0 100644 --- a/shared/mesh-chat/generate.ts +++ b/shared/mesh-chat/generate.ts @@ -106,7 +106,9 @@ export async function generateResponse( ): Promise { const apiMessages = messagesToPrompt(messages, config.systemPrompt); - console.log("[MeshChat] ========== generateResponse (non-streaming) =========="); + console.log( + "[MeshChat] ========== generateResponse (non-streaming) ==========", + ); console.log("[MeshChat] Generating response", { messageCount: apiMessages.length, modelId: config.modelId, @@ -115,10 +117,10 @@ export async function generateResponse( meshUrl: config.meshUrl, }); - console.log({config, apiMessages}); + console.log({ config, apiMessages }); const startTime = Date.now(); const response = await callDecopilotAPI(config, apiMessages); - console.log({response}); + console.log({ response }); if (!response.body) { console.error("[MeshChat] No response body from Decopilot API!"); @@ -128,7 +130,9 @@ export async function generateResponse( console.log(`[MeshChat] Collecting stream text...`); const text = await collectFullStreamText(response.body); - console.log(`[MeshChat] Response fully collected in ${Date.now() - startTime}ms (${text.length} chars)`); + console.log( + `[MeshChat] Response fully collected in ${Date.now() - startTime}ms (${text.length} chars)`, + ); console.log(`[MeshChat] Response preview: "${text.substring(0, 300)}"`); return text || "Desculpe, não consegui gerar uma resposta."; @@ -161,13 +165,17 @@ export async function generateResponseWithStreaming( const response = await callDecopilotAPI(config, apiMessages); if (!response.body) { - console.error("[MeshChat] No response body from Decopilot API (streaming)!"); + console.error( + "[MeshChat] No response body from Decopilot API (streaming)!", + ); throw new Error("No response body from Decopilot API"); } console.log(`[MeshChat] Starting stream processing...`); const result = await processStreamWithCallback(response.body, onStream); - console.log(`[MeshChat] Streaming completed in ${Date.now() - startTime}ms (${result.length} chars)`); + console.log( + `[MeshChat] Streaming completed in ${Date.now() - startTime}ms (${result.length} chars)`, + ); return result; } diff --git a/slack-mcp/server/lib/llm.ts b/slack-mcp/server/lib/llm.ts index 1bae1349..c6827752 100644 --- a/slack-mcp/server/lib/llm.ts +++ b/slack-mcp/server/lib/llm.ts @@ -49,22 +49,35 @@ export async function generateLLMResponse( messages: Message[], config: MeshChatConfig, ): Promise { - console.log(`[LLM] ========== generateLLMResponse (non-streaming) ==========`); - console.log(`[LLM] Config: meshUrl=${config.meshUrl}, model=${config.modelId}, agentId=${config.agentId ?? "none"}, org=${config.organizationId}`); + console.log( + `[LLM] ========== generateLLMResponse (non-streaming) ==========`, + ); + console.log( + `[LLM] Config: meshUrl=${config.meshUrl}, model=${config.modelId}, agentId=${config.agentId ?? "none"}, org=${config.organizationId}`, + ); console.log(`[LLM] Messages: ${messages.length} total`); messages.forEach((m, i) => { - console.log(`[LLM] [${i}] role=${m.role}, content length=${m.content.length}, images=${m.images?.length ?? 0}`); + console.log( + `[LLM] [${i}] role=${m.role}, content length=${m.content.length}, images=${m.images?.length ?? 0}`, + ); }); - console.log(`[LLM] Has system prompt: ${!!config.systemPrompt}, system prompt length: ${config.systemPrompt?.length ?? 0}`); + console.log( + `[LLM] Has system prompt: ${!!config.systemPrompt}, system prompt length: ${config.systemPrompt?.length ?? 0}`, + ); const startTime = Date.now(); try { const result = await generateResponse(config, toSharedMessages(messages)); - console.log(`[LLM] Response received in ${Date.now() - startTime}ms. Length: ${result.length} chars`); + console.log( + `[LLM] Response received in ${Date.now() - startTime}ms. Length: ${result.length} chars`, + ); console.log(`[LLM] Response preview: "${result.substring(0, 300)}"`); return result; } catch (error) { - console.error(`[LLM] generateLLMResponse FAILED after ${Date.now() - startTime}ms:`, error); + console.error( + `[LLM] generateLLMResponse FAILED after ${Date.now() - startTime}ms:`, + error, + ); throw error; } } @@ -80,12 +93,18 @@ export async function generateLLMResponseWithStreaming( onStream: StreamCallback, ): Promise { console.log(`[LLM] ========== generateLLMResponseWithStreaming ==========`); - console.log(`[LLM] Config: meshUrl=${config.meshUrl}, model=${config.modelId}, agentId=${config.agentId ?? "none"}, org=${config.organizationId}`); + console.log( + `[LLM] Config: meshUrl=${config.meshUrl}, model=${config.modelId}, agentId=${config.agentId ?? "none"}, org=${config.organizationId}`, + ); console.log(`[LLM] Messages: ${messages.length} total`); messages.forEach((m, i) => { - console.log(`[LLM] [${i}] role=${m.role}, content length=${m.content.length}, images=${m.images?.length ?? 0}`); + console.log( + `[LLM] [${i}] role=${m.role}, content length=${m.content.length}, images=${m.images?.length ?? 0}`, + ); }); - console.log(`[LLM] Has system prompt: ${!!config.systemPrompt}, system prompt length: ${config.systemPrompt?.length ?? 0}`); + console.log( + `[LLM] Has system prompt: ${!!config.systemPrompt}, system prompt length: ${config.systemPrompt?.length ?? 0}`, + ); const startTime = Date.now(); let chunkCount = 0; @@ -96,16 +115,25 @@ export async function generateLLMResponseWithStreaming( (text, isComplete) => { chunkCount++; if (isComplete) { - console.log(`[LLM] Streaming complete. Total chunks: ${chunkCount}, final length: ${text.length} chars, time: ${Date.now() - startTime}ms`); - console.log(`[LLM] Streaming response preview: "${text.substring(0, 300)}"`); + console.log( + `[LLM] Streaming complete. Total chunks: ${chunkCount}, final length: ${text.length} chars, time: ${Date.now() - startTime}ms`, + ); + console.log( + `[LLM] Streaming response preview: "${text.substring(0, 300)}"`, + ); } onStream(text, isComplete); }, ); - console.log(`[LLM] generateLLMResponseWithStreaming finished in ${Date.now() - startTime}ms`); + console.log( + `[LLM] generateLLMResponseWithStreaming finished in ${Date.now() - startTime}ms`, + ); return result; } catch (error) { - console.error(`[LLM] generateLLMResponseWithStreaming FAILED after ${Date.now() - startTime}ms (chunks received: ${chunkCount}):`, error); + console.error( + `[LLM] generateLLMResponseWithStreaming FAILED after ${Date.now() - startTime}ms (chunks received: ${chunkCount}):`, + error, + ); throw error; } } diff --git a/slack-mcp/server/lib/slack-client.ts b/slack-mcp/server/lib/slack-client.ts index c28a28bd..8588e378 100644 --- a/slack-mcp/server/lib/slack-client.ts +++ b/slack-mcp/server/lib/slack-client.ts @@ -231,8 +231,12 @@ export async function sendMessage( return null; } - console.log(`[Slack] sendMessage: channel=${options.channel}, threadTs=${options.threadTs ?? "none"}, text length=${options.text?.length ?? 0}, has blocks=${!!options.blocks}`); - console.log(`[Slack] sendMessage text preview: "${(options.text ?? "").substring(0, 150)}"`); + console.log( + `[Slack] sendMessage: channel=${options.channel}, threadTs=${options.threadTs ?? "none"}, text length=${options.text?.length ?? 0}, has blocks=${!!options.blocks}`, + ); + console.log( + `[Slack] sendMessage text preview: "${(options.text ?? "").substring(0, 150)}"`, + ); try { const startTime = Date.now(); @@ -246,7 +250,9 @@ export async function sendMessage( mrkdwn: options.mrkdwn ?? true, }); - console.log(`[Slack] sendMessage SUCCESS in ${Date.now() - startTime}ms. Response TS: ${result.ts}, OK: ${result.ok}`); + console.log( + `[Slack] sendMessage SUCCESS in ${Date.now() - startTime}ms. Response TS: ${result.ts}, OK: ${result.ok}`, + ); return result; } catch (error) { console.error("[Slack] sendMessage FAILED:", error); @@ -282,7 +288,9 @@ export async function updateMessage( ): Promise { if (!webClient) return false; - console.log(`[Slack] updateMessage: channel=${channel}, ts=${ts}, text length=${text.length}, has blocks=${!!blocks}`); + console.log( + `[Slack] updateMessage: channel=${channel}, ts=${ts}, text length=${text.length}, has blocks=${!!blocks}`, + ); try { const startTime = Date.now(); @@ -335,7 +343,9 @@ export async function getChannelHistory( ): Promise { if (!webClient) return []; - console.log(`[Slack] getChannelHistory: channel=${channel}, limit=${options.limit ?? 100}, latest=${options.latest ?? "none"}`); + console.log( + `[Slack] getChannelHistory: channel=${channel}, limit=${options.limit ?? 100}, latest=${options.latest ?? "none"}`, + ); try { const startTime = Date.now(); @@ -348,7 +358,9 @@ export async function getChannelHistory( }); const messages = (result.messages as SlackMessage[]) ?? []; - console.log(`[Slack] getChannelHistory: ${messages.length} messages fetched in ${Date.now() - startTime}ms`); + console.log( + `[Slack] getChannelHistory: ${messages.length} messages fetched in ${Date.now() - startTime}ms`, + ); return messages; } catch (error) { console.error("[Slack] getChannelHistory FAILED:", error); @@ -366,7 +378,9 @@ export async function getThreadReplies( ): Promise { if (!webClient) return []; - console.log(`[Slack] getThreadReplies: channel=${channel}, threadTs=${threadTs}, limit=${limit}`); + console.log( + `[Slack] getThreadReplies: channel=${channel}, threadTs=${threadTs}, limit=${limit}`, + ); try { const startTime = Date.now(); @@ -377,7 +391,9 @@ export async function getThreadReplies( }); const messages = (result.messages as SlackMessage[]) ?? []; - console.log(`[Slack] getThreadReplies: ${messages.length} messages fetched in ${Date.now() - startTime}ms`); + console.log( + `[Slack] getThreadReplies: ${messages.length} messages fetched in ${Date.now() - startTime}ms`, + ); return messages; } catch (error) { console.error("[Slack] getThreadReplies FAILED:", error); diff --git a/slack-mcp/server/router.ts b/slack-mcp/server/router.ts index 92a067e3..ab19fd54 100644 --- a/slack-mcp/server/router.ts +++ b/slack-mcp/server/router.ts @@ -347,14 +347,30 @@ async function processConnectionEventAsync( return; } - console.log(`[Router] [${traceId}] ========== START processConnectionEventAsync ==========`); - console.log(`[Router] [${traceId}] Event type: ${payload.event.type}, subtype: ${payload.event.subtype ?? "none"}`); - console.log(`[Router] [${traceId}] Channel: ${payload.event.channel}, User: ${payload.event.user}, TS: ${payload.event.ts}`); - console.log(`[Router] [${traceId}] Thread TS: ${payload.event.thread_ts ?? "none"}`); - console.log(`[Router] [${traceId}] Text: ${(payload.event.text ?? "").substring(0, 200)}`); - console.log(`[Router] [${traceId}] Connection: ${connectionConfig.connectionId}, Team: ${connectionConfig.teamId}`); - console.log(`[Router] [${traceId}] Has files: ${!!payload.event.files}, File count: ${payload.event.files?.length ?? 0}`); - console.log(`[Router] [${traceId}] Bot user ID from config: ${connectionConfig.botUserId ?? "not set"}`); + console.log( + `[Router] [${traceId}] ========== START processConnectionEventAsync ==========`, + ); + console.log( + `[Router] [${traceId}] Event type: ${payload.event.type}, subtype: ${payload.event.subtype ?? "none"}`, + ); + console.log( + `[Router] [${traceId}] Channel: ${payload.event.channel}, User: ${payload.event.user}, TS: ${payload.event.ts}`, + ); + console.log( + `[Router] [${traceId}] Thread TS: ${payload.event.thread_ts ?? "none"}`, + ); + console.log( + `[Router] [${traceId}] Text: ${(payload.event.text ?? "").substring(0, 200)}`, + ); + console.log( + `[Router] [${traceId}] Connection: ${connectionConfig.connectionId}, Team: ${connectionConfig.teamId}`, + ); + console.log( + `[Router] [${traceId}] Has files: ${!!payload.event.files}, File count: ${payload.event.files?.length ?? 0}`, + ); + console.log( + `[Router] [${traceId}] Bot user ID from config: ${connectionConfig.botUserId ?? "not set"}`, + ); // IMPORTANT: Initialize Slack client with this connection's token // Each connection has its own Slack workspace credentials @@ -364,8 +380,12 @@ async function processConnectionEventAsync( // Falls back to FALLBACK_MODEL_ID when modelId is not configured if (connectionConfig.meshToken) { const modelId = connectionConfig.modelId ?? FALLBACK_MODEL_ID; - console.log(`[Router] [${traceId}] Configuring LLM: model=${modelId}, agentId=${connectionConfig.agentId ?? "none"}, meshUrl=${connectionConfig.meshUrl}`); - console.log(`[Router] [${traceId}] Has system prompt: ${!!connectionConfig.systemPrompt}, prompt length: ${connectionConfig.systemPrompt?.length ?? 0}`); + console.log( + `[Router] [${traceId}] Configuring LLM: model=${modelId}, agentId=${connectionConfig.agentId ?? "none"}, meshUrl=${connectionConfig.meshUrl}`, + ); + console.log( + `[Router] [${traceId}] Has system prompt: ${!!connectionConfig.systemPrompt}, prompt length: ${connectionConfig.systemPrompt?.length ?? 0}`, + ); configureLLM({ meshUrl: connectionConfig.meshUrl, organizationId: connectionConfig.organizationId, @@ -378,7 +398,9 @@ async function processConnectionEventAsync( } else { // Clear LLM config to prevent cross-tenant configuration leakage clearLLMConfig(); - console.log(`[Router] [${traceId}] LLM NOT configured - missing meshToken for connection ${connectionConfig.connectionId}`); + console.log( + `[Router] [${traceId}] LLM NOT configured - missing meshToken for connection ${connectionConfig.connectionId}`, + ); logger.warn("LLM not configured - missing meshToken", { connectionId: connectionConfig.connectionId, trace_id: traceId, @@ -391,7 +413,9 @@ async function processConnectionEventAsync( const enableStreaming = showOnlyFinal ? false : (connectionConfig.responseConfig?.enableStreaming ?? true); - console.log(`[Router] [${traceId}] Response config: showOnlyFinal=${showOnlyFinal}, streaming=${enableStreaming}, showThinking=${connectionConfig.responseConfig?.showThinkingMessage ?? "default(true)"}`); + console.log( + `[Router] [${traceId}] Response config: showOnlyFinal=${showOnlyFinal}, streaming=${enableStreaming}, showThinking=${connectionConfig.responseConfig?.showThinkingMessage ?? "default(true)"}`, + ); configureStreaming(enableStreaming); const event = payload.event; @@ -409,7 +433,9 @@ async function processConnectionEventAsync( if (botUserId) { cleanText = removeBotMention(cleanText, botUserId); } - console.log(`[Router] [${traceId}] Event is app_mention, will process. Clean text: "${cleanText.substring(0, 100)}"`); + console.log( + `[Router] [${traceId}] Event is app_mention, will process. Clean text: "${cleanText.substring(0, 100)}"`, + ); } else if (eventType === "message") { const isDM = event.channel?.startsWith("D"); if (isDM) { @@ -418,13 +444,19 @@ async function processConnectionEventAsync( } else if (botUserId && isBotMentioned(event.text ?? "", botUserId)) { shouldProcess = true; cleanText = removeBotMention(event.text ?? "", botUserId); - console.log(`[Router] [${traceId}] Event is channel message with bot mention, will process`); + console.log( + `[Router] [${traceId}] Event is channel message with bot mention, will process`, + ); } else { - console.log(`[Router] [${traceId}] Event is channel message without bot mention, checking if should skip. botUserId=${botUserId}, text contains mention: ${botUserId ? (event.text ?? "").includes(`<@${botUserId}>`) : "N/A"}`); + console.log( + `[Router] [${traceId}] Event is channel message without bot mention, checking if should skip. botUserId=${botUserId}, text contains mention: ${botUserId ? (event.text ?? "").includes(`<@${botUserId}>`) : "N/A"}`, + ); } } else { shouldProcess = true; - console.log(`[Router] [${traceId}] Event type ${eventType} will be processed`); + console.log( + `[Router] [${traceId}] Event type ${eventType} will be processed`, + ); } if (shouldProcess) { @@ -454,10 +486,16 @@ async function processConnectionEventAsync( }, teamConfig, ); - console.log(`[Router] [${traceId}] ========== END processConnectionEventAsync (success) ==========`); + console.log( + `[Router] [${traceId}] ========== END processConnectionEventAsync (success) ==========`, + ); } else { - console.log(`[Router] [${traceId}] Event NOT processed (shouldProcess=false)`); - console.log(`[Router] [${traceId}] ========== END processConnectionEventAsync (skipped) ==========`); + console.log( + `[Router] [${traceId}] Event NOT processed (shouldProcess=false)`, + ); + console.log( + `[Router] [${traceId}] ========== END processConnectionEventAsync (skipped) ==========`, + ); } } diff --git a/slack-mcp/server/slack/handlers/context-builder.ts b/slack-mcp/server/slack/handlers/context-builder.ts index 49e6a697..556bfa6a 100644 --- a/slack-mcp/server/slack/handlers/context-builder.ts +++ b/slack-mcp/server/slack/handlers/context-builder.ts @@ -105,8 +105,12 @@ export async function buildContextMessages( limit: number = contextConfig.maxMessagesToFetch, ): Promise { console.log(`[ContextBuilder] ========== buildContextMessages ==========`); - console.log(`[ContextBuilder] Channel: ${channel}, threadTs: ${threadTs ?? "none"}, currentTs: ${currentTs}, limit: ${limit}`); - console.log(`[ContextBuilder] Config: maxFetch=${contextConfig.maxMessagesToFetch}, maxBeforeSummary=${contextConfig.maxMessagesBeforeSummary}, recentToKeep=${contextConfig.recentMessagesToKeep}`); + console.log( + `[ContextBuilder] Channel: ${channel}, threadTs: ${threadTs ?? "none"}, currentTs: ${currentTs}, limit: ${limit}`, + ); + console.log( + `[ContextBuilder] Config: maxFetch=${contextConfig.maxMessagesToFetch}, maxBeforeSummary=${contextConfig.maxMessagesBeforeSummary}, recentToKeep=${contextConfig.recentMessagesToKeep}`, + ); console.log(`[ContextBuilder] Bot user ID: ${globalBotUserId ?? "not set"}`); const messages: ContextMessage[] = []; @@ -122,22 +126,30 @@ export async function buildContextMessages( threadTs, contextConfig.maxMessagesToFetch, ); - console.log(`[ContextBuilder] Thread replies fetched: ${threadMessages.length} raw messages in ${Date.now() - fetchStartTime}ms`); + console.log( + `[ContextBuilder] Thread replies fetched: ${threadMessages.length} raw messages in ${Date.now() - fetchStartTime}ms`, + ); allMessages = threadMessages .filter((msg) => msg.ts !== currentTs && msg.text) .sort((a, b) => Number.parseFloat(a.ts) - Number.parseFloat(b.ts)); - console.log(`[ContextBuilder] After filtering: ${allMessages.length} messages (excluded currentTs=${currentTs})`); + console.log( + `[ContextBuilder] After filtering: ${allMessages.length} messages (excluded currentTs=${currentTs})`, + ); } else { console.log(`[ContextBuilder] Fetching channel history...`); const channelMessages = await getChannelHistory(channel, { limit: contextConfig.maxMessagesToFetch, latest: currentTs, }); - console.log(`[ContextBuilder] Channel history fetched: ${channelMessages.length} raw messages in ${Date.now() - fetchStartTime}ms`); + console.log( + `[ContextBuilder] Channel history fetched: ${channelMessages.length} raw messages in ${Date.now() - fetchStartTime}ms`, + ); allMessages = channelMessages .filter((msg) => msg.ts !== currentTs && msg.text) .sort((a, b) => Number.parseFloat(a.ts) - Number.parseFloat(b.ts)); - console.log(`[ContextBuilder] After filtering: ${allMessages.length} messages`); + console.log( + `[ContextBuilder] After filtering: ${allMessages.length} messages`, + ); } // Convert to role-based messages @@ -148,9 +160,13 @@ export async function buildContextMessages( content: msg.text, })); - console.log(`[ContextBuilder] Role messages: ${roleMessages.length} (user: ${roleMessages.filter(m => m.role === "user").length}, assistant: ${roleMessages.filter(m => m.role === "assistant").length})`); + console.log( + `[ContextBuilder] Role messages: ${roleMessages.length} (user: ${roleMessages.filter((m) => m.role === "user").length}, assistant: ${roleMessages.filter((m) => m.role === "assistant").length})`, + ); roleMessages.forEach((msg, i) => { - console.log(`[ContextBuilder] [${i}] ${msg.role}: "${msg.content.substring(0, 80)}${msg.content.length > 80 ? "..." : ""}"`); + console.log( + `[ContextBuilder] [${i}] ${msg.role}: "${msg.content.substring(0, 80)}${msg.content.length > 80 ? "..." : ""}"`, + ); }); // Check if we need to summarize @@ -171,7 +187,9 @@ export async function buildContextMessages( ); } else { messages.push(...roleMessages.slice(-limit)); - console.log(`[ContextBuilder] Using ${messages.length} messages (no summary needed)`); + console.log( + `[ContextBuilder] Using ${messages.length} messages (no summary needed)`, + ); } console.log(`[ContextBuilder] Final context: ${messages.length} messages`); diff --git a/slack-mcp/server/slack/handlers/eventHandler.ts b/slack-mcp/server/slack/handlers/eventHandler.ts index 4dff512c..115f6001 100644 --- a/slack-mcp/server/slack/handlers/eventHandler.ts +++ b/slack-mcp/server/slack/handlers/eventHandler.ts @@ -472,12 +472,24 @@ export async function handleSlackEvent( console.log(`[EventHandler] ========== handleSlackEvent ==========`); console.log(`[EventHandler] Event type: ${type}`); - console.log(`[EventHandler] Channel: ${payload.channel}, User: ${payload.user}, TS: ${payload.ts}`); - console.log(`[EventHandler] Thread TS: ${payload.thread_ts ?? "none"}, Team: ${teamConfig.teamId}`); - console.log(`[EventHandler] Text preview: "${(payload.text ?? "").substring(0, 150)}"`); - console.log(`[EventHandler] Has files: ${!!payload.files}, Channel type: ${payload.channel_type ?? "unknown"}`); - console.log(`[EventHandler] Bot user ID (global): ${globalBotUserId ?? "not set"}, Bot user ID (team): ${teamConfig.botUserId ?? "not set"}`); - console.log(`[EventHandler] Response config: showOnlyFinal=${teamConfig.responseConfig?.showOnlyFinalResponse ?? false}, streaming=${teamConfig.responseConfig?.enableStreaming ?? "default"}, thinking=${teamConfig.responseConfig?.showThinkingMessage ?? "default"}`); + console.log( + `[EventHandler] Channel: ${payload.channel}, User: ${payload.user}, TS: ${payload.ts}`, + ); + console.log( + `[EventHandler] Thread TS: ${payload.thread_ts ?? "none"}, Team: ${teamConfig.teamId}`, + ); + console.log( + `[EventHandler] Text preview: "${(payload.text ?? "").substring(0, 150)}"`, + ); + console.log( + `[EventHandler] Has files: ${!!payload.files}, Channel type: ${payload.channel_type ?? "unknown"}`, + ); + console.log( + `[EventHandler] Bot user ID (global): ${globalBotUserId ?? "not set"}, Bot user ID (team): ${teamConfig.botUserId ?? "not set"}`, + ); + console.log( + `[EventHandler] Response config: showOnlyFinal=${teamConfig.responseConfig?.showOnlyFinalResponse ?? false}, streaming=${teamConfig.responseConfig?.enableStreaming ?? "default"}, thinking=${teamConfig.responseConfig?.showThinkingMessage ?? "default"}`, + ); switch (type) { case "app_mention": @@ -509,7 +521,9 @@ async function handleAppMention( ): Promise { const { channel, user, text, ts, thread_ts, files } = event; console.log(`[EventHandler] ========== handleAppMention ==========`); - console.log(`[EventHandler] From user: ${user}, Channel: ${channel}, TS: ${ts}`); + console.log( + `[EventHandler] From user: ${user}, Channel: ${channel}, TS: ${ts}`, + ); console.log(`[EventHandler] Thread TS: ${thread_ts ?? "new thread"}`); console.log(`[EventHandler] Full text: "${text}"`); console.log(`[EventHandler] Files attached: ${files?.length ?? 0}`); @@ -587,7 +601,9 @@ async function handleAppMention( } // Build messages for LLM - console.log(`[EventHandler] Building LLM messages for app_mention. Full text length: ${fullText.length}, media count: ${mediaForLLM.length}`); + console.log( + `[EventHandler] Building LLM messages for app_mention. Full text length: ${fullText.length}, media count: ${mediaForLLM.length}`, + ); const messages = await buildLLMMessages( channel, fullText, @@ -596,9 +612,13 @@ async function handleAppMention( mediaForLLM, true, // Clean bot mention ); - console.log(`[EventHandler] LLM messages built: ${messages.length} messages total`); + console.log( + `[EventHandler] LLM messages built: ${messages.length} messages total`, + ); messages.forEach((msg, i) => { - console.log(`[EventHandler] Message[${i}]: role=${msg.role}, content length=${msg.content.length}, has images=${!!msg.images}, content preview="${msg.content.substring(0, 100)}"`); + console.log( + `[EventHandler] Message[${i}]: role=${msg.role}, content length=${msg.content.length}, has images=${!!msg.images}, content preview="${msg.content.substring(0, 100)}"`, + ); }); // Check if LLM is configured @@ -615,7 +635,9 @@ async function handleAppMention( } // Call LLM - console.log(`[EventHandler] Calling LLM for app_mention. Channel: ${channel}, replyTo: ${replyTo}, thinkingTs: ${thinkingMsg?.ts ?? "none"}`); + console.log( + `[EventHandler] Calling LLM for app_mention. Channel: ${channel}, replyTo: ${replyTo}, thinkingTs: ${thinkingMsg?.ts ?? "none"}`, + ); const llmStartTime = Date.now(); try { await handleLLMCall(messages, { @@ -623,9 +645,14 @@ async function handleAppMention( replyTo, thinkingMessageTs: thinkingMsg?.ts, }); - console.log(`[EventHandler] LLM call completed for app_mention in ${Date.now() - llmStartTime}ms`); + console.log( + `[EventHandler] LLM call completed for app_mention in ${Date.now() - llmStartTime}ms`, + ); } catch (error) { - console.error(`[EventHandler] LLM call FAILED for app_mention after ${Date.now() - llmStartTime}ms:`, error); + console.error( + `[EventHandler] LLM call FAILED for app_mention after ${Date.now() - llmStartTime}ms:`, + error, + ); logger.error("App mention LLM error", { channel, userId: user, @@ -646,28 +673,42 @@ async function handleMessage( const botUserId = globalBotUserId ?? teamConfig.botUserId; console.log(`[EventHandler] ========== handleMessage ==========`); - console.log(`[EventHandler] From user: ${user}, Channel: ${channel}, TS: ${ts}`); - console.log(`[EventHandler] isDM: ${isDM}, channel_type: ${channel_type ?? "unknown"}, thread_ts: ${thread_ts ?? "none"}`); + console.log( + `[EventHandler] From user: ${user}, Channel: ${channel}, TS: ${ts}`, + ); + console.log( + `[EventHandler] isDM: ${isDM}, channel_type: ${channel_type ?? "unknown"}, thread_ts: ${thread_ts ?? "none"}`, + ); console.log(`[EventHandler] Text: "${(text ?? "").substring(0, 200)}"`); - console.log(`[EventHandler] Files: ${files?.length ?? 0}, botUserId: ${botUserId ?? "not set"}`); + console.log( + `[EventHandler] Files: ${files?.length ?? 0}, botUserId: ${botUserId ?? "not set"}`, + ); // Skip messages with bot mention in channels (handled by app_mention) if (!isDM && botUserId && text?.includes(`<@${botUserId}>`)) { - console.log("[EventHandler] Skipping message - will be handled by app_mention event instead"); + console.log( + "[EventHandler] Skipping message - will be handled by app_mention event instead", + ); return; } // For threads, check if bot participated if (thread_ts && !isDM) { - console.log(`[EventHandler] Checking bot participation in thread ${thread_ts}...`); + console.log( + `[EventHandler] Checking bot participation in thread ${thread_ts}...`, + ); const botParticipated = await botParticipatedInThread( channel, thread_ts, botUserId ?? null, ); - console.log(`[EventHandler] Bot participated in thread: ${botParticipated}`); + console.log( + `[EventHandler] Bot participated in thread: ${botParticipated}`, + ); if (!botParticipated) { - console.log(`[EventHandler] Ignoring thread reply - bot not in thread ${thread_ts}`); + console.log( + `[EventHandler] Ignoring thread reply - bot not in thread ${thread_ts}`, + ); return; } } @@ -727,7 +768,9 @@ async function handleMessage( }; if (isDM) { - console.log(`[EventHandler] Routing to handleDirectMessage. Text length: ${fullText.length}, media: ${mediaForLLM.length}`); + console.log( + `[EventHandler] Routing to handleDirectMessage. Text length: ${fullText.length}, media: ${mediaForLLM.length}`, + ); await handleDirectMessage( channel, user, @@ -738,7 +781,9 @@ async function handleMessage( teamConfig, ); } else if (thread_ts) { - console.log(`[EventHandler] Routing to handleThreadReply. Thread: ${thread_ts}, text length: ${fullText.length}, media: ${mediaForLLM.length}`); + console.log( + `[EventHandler] Routing to handleThreadReply. Thread: ${thread_ts}, text length: ${fullText.length}, media: ${mediaForLLM.length}`, + ); await handleThreadReply( channel, user, @@ -771,7 +816,9 @@ async function handleDirectMessage( teamConfig: SlackTeamConfig, ): Promise { console.log(`[EventHandler] ========== handleDirectMessage ==========`); - console.log(`[EventHandler] DM from user: ${user}, Channel: ${channel}, TS: ${ts}`); + console.log( + `[EventHandler] DM from user: ${user}, Channel: ${channel}, TS: ${ts}`, + ); console.log(`[EventHandler] Text: "${text.substring(0, 200)}"`); console.log(`[EventHandler] Media count: ${media.length}`); @@ -791,7 +838,9 @@ async function handleDirectMessage( : (teamConfig.responseConfig?.showThinkingMessage ?? true); console.log(`[EventHandler] Sending thinking message: ${showThinking}`); const thinkingMsg = showThinking ? await sendThinkingMessage(channel) : null; - console.log(`[EventHandler] Thinking message TS: ${thinkingMsg?.ts ?? "not sent"}`); + console.log( + `[EventHandler] Thinking message TS: ${thinkingMsg?.ts ?? "not sent"}`, + ); // Remove eyes reaction when we start processing (unless in silent mode) if (!showOnlyFinal) { @@ -802,7 +851,9 @@ async function handleDirectMessage( const messages = await buildLLMMessages(channel, text, ts, undefined, media); console.log(`[EventHandler] LLM messages built: ${messages.length} messages`); messages.forEach((msg, i) => { - console.log(`[EventHandler] Message[${i}]: role=${msg.role}, content length=${msg.content.length}, has images=${!!msg.images}`); + console.log( + `[EventHandler] Message[${i}]: role=${msg.role}, content length=${msg.content.length}, has images=${!!msg.images}`, + ); }); if (!isLLMConfigured()) { @@ -823,16 +874,23 @@ async function handleDirectMessage( return; } - console.log(`[EventHandler] Calling LLM for DM. Channel: ${channel}, thinkingTs: ${thinkingMsg?.ts ?? "none"}`); + console.log( + `[EventHandler] Calling LLM for DM. Channel: ${channel}, thinkingTs: ${thinkingMsg?.ts ?? "none"}`, + ); const dmLlmStartTime = Date.now(); try { await handleLLMCall(messages, { channel, thinkingMessageTs: thinkingMsg?.ts, }); - console.log(`[EventHandler] LLM call completed for DM in ${Date.now() - dmLlmStartTime}ms`); + console.log( + `[EventHandler] LLM call completed for DM in ${Date.now() - dmLlmStartTime}ms`, + ); } catch (error) { - console.error(`[EventHandler] LLM call FAILED for DM after ${Date.now() - dmLlmStartTime}ms:`, error); + console.error( + `[EventHandler] LLM call FAILED for DM after ${Date.now() - dmLlmStartTime}ms:`, + error, + ); logger.error("Direct message LLM error", { channel, userId: user, @@ -863,7 +921,9 @@ async function handleThreadReply( teamConfig: SlackTeamConfig, ): Promise { console.log(`[EventHandler] ========== handleThreadReply ==========`); - console.log(`[EventHandler] Thread reply from user: ${user}, Channel: ${channel}, TS: ${ts}, ThreadTS: ${threadTs}`); + console.log( + `[EventHandler] Thread reply from user: ${user}, Channel: ${channel}, TS: ${ts}, ThreadTS: ${threadTs}`, + ); console.log(`[EventHandler] Text: "${text.substring(0, 200)}"`); console.log(`[EventHandler] Media count: ${media.length}`); @@ -881,11 +941,15 @@ async function handleThreadReply( const showThinking = showOnlyFinal ? false : (teamConfig.responseConfig?.showThinkingMessage ?? true); - console.log(`[EventHandler] Sending thinking message in thread ${threadTs}: ${showThinking}`); + console.log( + `[EventHandler] Sending thinking message in thread ${threadTs}: ${showThinking}`, + ); const thinkingMsg = showThinking ? await sendThinkingMessage(channel, threadTs) : null; - console.log(`[EventHandler] Thinking message TS: ${thinkingMsg?.ts ?? "not sent"}`); + console.log( + `[EventHandler] Thinking message TS: ${thinkingMsg?.ts ?? "not sent"}`, + ); // Remove eyes reaction when we start processing (unless in silent mode) if (!showOnlyFinal) { @@ -896,7 +960,9 @@ async function handleThreadReply( const messages = await buildLLMMessages(channel, text, ts, threadTs, media); console.log(`[EventHandler] LLM messages built: ${messages.length} messages`); messages.forEach((msg, i) => { - console.log(`[EventHandler] Message[${i}]: role=${msg.role}, content length=${msg.content.length}, has images=${!!msg.images}`); + console.log( + `[EventHandler] Message[${i}]: role=${msg.role}, content length=${msg.content.length}, has images=${!!msg.images}`, + ); }); if (!isLLMConfigured()) { @@ -917,7 +983,9 @@ async function handleThreadReply( return; } - console.log(`[EventHandler] Calling LLM for thread reply. Channel: ${channel}, threadTs: ${threadTs}, thinkingTs: ${thinkingMsg?.ts ?? "none"}`); + console.log( + `[EventHandler] Calling LLM for thread reply. Channel: ${channel}, threadTs: ${threadTs}, thinkingTs: ${thinkingMsg?.ts ?? "none"}`, + ); const threadLlmStartTime = Date.now(); try { await handleLLMCall(messages, { @@ -925,9 +993,14 @@ async function handleThreadReply( replyTo: threadTs, thinkingMessageTs: thinkingMsg?.ts, }); - console.log(`[EventHandler] LLM call completed for thread reply in ${Date.now() - threadLlmStartTime}ms`); + console.log( + `[EventHandler] LLM call completed for thread reply in ${Date.now() - threadLlmStartTime}ms`, + ); } catch (error) { - console.error(`[EventHandler] LLM call FAILED for thread reply after ${Date.now() - threadLlmStartTime}ms:`, error); + console.error( + `[EventHandler] LLM call FAILED for thread reply after ${Date.now() - threadLlmStartTime}ms:`, + error, + ); logger.error("Thread reply LLM error", { channel, userId: user, diff --git a/slack-mcp/server/slack/handlers/llm-handler.ts b/slack-mcp/server/slack/handlers/llm-handler.ts index 8b9d162e..9366e892 100644 --- a/slack-mcp/server/slack/handlers/llm-handler.ts +++ b/slack-mcp/server/slack/handlers/llm-handler.ts @@ -135,10 +135,14 @@ export async function callLLMWithStreaming( const { channel, thinkingMessageTs, useBlocks = true } = options; console.log(`[LLMHandler] ========== callLLMWithStreaming ==========`); - console.log(`[LLMHandler] Channel: ${channel}, thinkingTs: ${thinkingMessageTs ?? "none"}`); + console.log( + `[LLMHandler] Channel: ${channel}, thinkingTs: ${thinkingMessageTs ?? "none"}`, + ); if (!thinkingMessageTs) { - console.log(`[LLMHandler] No thinking message, falling back to non-streaming`); + console.log( + `[LLMHandler] No thinking message, falling back to non-streaming`, + ); return callLLMWithoutStreaming(messages, options); } @@ -154,17 +158,25 @@ export async function callLLMWithStreaming( if (!isComplete) return; animation.stop(); - console.log(`[LLMHandler] Streaming complete. Response length: ${text.length} chars, time: ${Date.now() - streamStartTime}ms`); - console.log(`[LLMHandler] Response preview: "${text.substring(0, 200)}"`); + console.log( + `[LLMHandler] Streaming complete. Response length: ${text.length} chars, time: ${Date.now() - streamStartTime}ms`, + ); + console.log( + `[LLMHandler] Response preview: "${text.substring(0, 200)}"`, + ); const formattedText = formatForSlack(text); - console.log(`[LLMHandler] Formatted text length: ${formattedText.length}, using blocks: ${useBlocks && text.length > 500}`); + console.log( + `[LLMHandler] Formatted text length: ${formattedText.length}, using blocks: ${useBlocks && text.length > 500}`, + ); const blocks = useBlocks && text.length > 500 ? buildResponseBlocks(text, { addFeedbackButtons: false }) : undefined; - console.log(`[LLMHandler] Updating thinking message ${thinkingMessageTs} with final response`); + console.log( + `[LLMHandler] Updating thinking message ${thinkingMessageTs} with final response`, + ); await updateThinkingMessage( channel, thinkingMessageTs, @@ -195,21 +207,29 @@ export async function callLLMWithoutStreaming( const { channel, replyTo, thinkingMessageTs, useBlocks = true } = options; console.log(`[LLMHandler] ========== callLLMWithoutStreaming ==========`); - console.log(`[LLMHandler] Channel: ${channel}, replyTo: ${replyTo ?? "none"}, thinkingTs: ${thinkingMessageTs ?? "none"}`); + console.log( + `[LLMHandler] Channel: ${channel}, replyTo: ${replyTo ?? "none"}, thinkingTs: ${thinkingMessageTs ?? "none"}`, + ); const nonStreamStartTime = Date.now(); console.log(`[LLMHandler] Starting non-streaming LLM call...`); const response = await generateLLMResponse(messages, globalLLMConfig); - console.log(`[LLMHandler] Non-streaming LLM response received in ${Date.now() - nonStreamStartTime}ms. Length: ${response.length} chars`); + console.log( + `[LLMHandler] Non-streaming LLM response received in ${Date.now() - nonStreamStartTime}ms. Length: ${response.length} chars`, + ); console.log(`[LLMHandler] Response preview: "${response.substring(0, 200)}"`); const formattedResponse = formatForSlack(response); - console.log(`[LLMHandler] Formatted response length: ${formattedResponse.length}`); + console.log( + `[LLMHandler] Formatted response length: ${formattedResponse.length}`, + ); const blocks = useBlocks && response.length > 500 ? buildResponseBlocks(response, { addFeedbackButtons: false }) : undefined; if (thinkingMessageTs) { - console.log(`[LLMHandler] Updating thinking message ${thinkingMessageTs} with response`); + console.log( + `[LLMHandler] Updating thinking message ${thinkingMessageTs} with response`, + ); await updateThinkingMessage( channel, thinkingMessageTs, @@ -241,15 +261,25 @@ export async function handleLLMCall( const { channel, replyTo, thinkingMessageTs } = options; console.log(`[LLMHandler] ========== handleLLMCall ==========`); - console.log(`[LLMHandler] Channel: ${channel}, replyTo: ${replyTo ?? "none"}, thinkingTs: ${thinkingMessageTs ?? "none"}`); - console.log(`[LLMHandler] Messages count: ${messages.length}, streaming enabled: ${streamingEnabled}`); - console.log(`[LLMHandler] LLM config: model=${globalLLMConfig?.modelId ?? "NOT SET"}, agentId=${globalLLMConfig?.agentId ?? "none"}, meshUrl=${globalLLMConfig?.meshUrl ?? "NOT SET"}`); - console.log(`[LLMHandler] Has system prompt: ${!!globalLLMConfig?.systemPrompt}`); + console.log( + `[LLMHandler] Channel: ${channel}, replyTo: ${replyTo ?? "none"}, thinkingTs: ${thinkingMessageTs ?? "none"}`, + ); + console.log( + `[LLMHandler] Messages count: ${messages.length}, streaming enabled: ${streamingEnabled}`, + ); + console.log( + `[LLMHandler] LLM config: model=${globalLLMConfig?.modelId ?? "NOT SET"}, agentId=${globalLLMConfig?.agentId ?? "none"}, meshUrl=${globalLLMConfig?.meshUrl ?? "NOT SET"}`, + ); + console.log( + `[LLMHandler] Has system prompt: ${!!globalLLMConfig?.systemPrompt}`, + ); try { // Use streaming only if enabled AND we have a thinking message to update const useStreaming = streamingEnabled && !!thinkingMessageTs; - console.log(`[LLMHandler] Using streaming: ${useStreaming} (streamingEnabled=${streamingEnabled}, hasThinkingMsg=${!!thinkingMessageTs})`); + console.log( + `[LLMHandler] Using streaming: ${useStreaming} (streamingEnabled=${streamingEnabled}, hasThinkingMsg=${!!thinkingMessageTs})`, + ); const startTime = Date.now(); if (useStreaming) { @@ -257,7 +287,9 @@ export async function handleLLMCall( } else { await callLLMWithoutStreaming(messages, options); } - console.log(`[LLMHandler] LLM call completed successfully in ${Date.now() - startTime}ms`); + console.log( + `[LLMHandler] LLM call completed successfully in ${Date.now() - startTime}ms`, + ); } catch (error) { logger.error("LLM response failed", { channel, diff --git a/slack-mcp/server/types/env.ts b/slack-mcp/server/types/env.ts index de8ea751..26445d6a 100644 --- a/slack-mcp/server/types/env.ts +++ b/slack-mcp/server/types/env.ts @@ -12,7 +12,7 @@ export const StateSchema = z.object({ AGENT: BindingOf("@deco/agent") .optional() .describe("Agent with tools, resources and prompts"), - MODEL_PROVIDER: z + MODEL_PROVIDER: z .object({ __type: z.literal("@deco/llm"), value: z diff --git a/slack-mcp/server/webhook.ts b/slack-mcp/server/webhook.ts index fd3dea13..d6a1b029 100644 --- a/slack-mcp/server/webhook.ts +++ b/slack-mcp/server/webhook.ts @@ -139,13 +139,17 @@ export function shouldIgnoreEvent( // Ignore bot messages if (event.bot_id) { - console.log(`[Webhook] Ignoring event: bot_id=${event.bot_id}, type=${event.type}, user=${event.user}`); + console.log( + `[Webhook] Ignoring event: bot_id=${event.bot_id}, type=${event.type}, user=${event.user}`, + ); return true; } // Ignore messages from ourselves if (botUserId && event.user === botUserId) { - console.log(`[Webhook] Ignoring event: message from ourselves (botUserId=${botUserId})`); + console.log( + `[Webhook] Ignoring event: message from ourselves (botUserId=${botUserId})`, + ); return true; } @@ -171,11 +175,15 @@ export function shouldIgnoreEvent( ]; if (event.subtype && ignoredSubtypes.includes(event.subtype)) { - console.log(`[Webhook] Ignoring event: subtype=${event.subtype}, type=${event.type}`); + console.log( + `[Webhook] Ignoring event: subtype=${event.subtype}, type=${event.type}`, + ); return true; } - console.log(`[Webhook] Event NOT ignored: type=${event.type}, subtype=${event.subtype ?? "none"}, user=${event.user}, bot_id=${event.bot_id ?? "none"}`); + console.log( + `[Webhook] Event NOT ignored: type=${event.type}, subtype=${event.subtype ?? "none"}, user=${event.user}, bot_id=${event.bot_id ?? "none"}`, + ); return false; } From 0355def390f4e3a86afc0d391ead45afeb8c0382 Mon Sep 17 00:00:00 2001 From: gimenes Date: Mon, 23 Mar 2026 10:59:01 -0300 Subject: [PATCH 3/5] fix: resolve TypeScript errors in openrouter and slack-mcp - Remove unused OpenRouterClient import in list-models.ts - Make streaming callback async to match StreamCallback type - Cast to any for files/channel_type on SlackEvent in debug logs Co-Authored-By: Claude Opus 4.6 (1M context) --- openrouter/server/tools/embeddings/list-models.ts | 1 - slack-mcp/server/lib/llm.ts | 2 +- slack-mcp/server/router.ts | 2 +- slack-mcp/server/slack/handlers/eventHandler.ts | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/openrouter/server/tools/embeddings/list-models.ts b/openrouter/server/tools/embeddings/list-models.ts index 2164382b..9aad9446 100644 --- a/openrouter/server/tools/embeddings/list-models.ts +++ b/openrouter/server/tools/embeddings/list-models.ts @@ -6,7 +6,6 @@ import { createPrivateTool } from "@decocms/runtime/tools"; import { getOpenRouterApiKey } from "server/lib/env.ts"; import { z } from "zod"; -import { OpenRouterClient } from "../../lib/openrouter-client.ts"; import type { Env } from "../../main.ts"; import { createCollectionBindings } from "@decocms/bindings/collections"; import { OpenRouter } from "@openrouter/sdk"; diff --git a/slack-mcp/server/lib/llm.ts b/slack-mcp/server/lib/llm.ts index c6827752..8a1d5999 100644 --- a/slack-mcp/server/lib/llm.ts +++ b/slack-mcp/server/lib/llm.ts @@ -112,7 +112,7 @@ export async function generateLLMResponseWithStreaming( const result = await generateResponseWithStreaming( config, toSharedMessages(messages), - (text, isComplete) => { + async (text, isComplete) => { chunkCount++; if (isComplete) { console.log( diff --git a/slack-mcp/server/router.ts b/slack-mcp/server/router.ts index ab19fd54..037fdc1b 100644 --- a/slack-mcp/server/router.ts +++ b/slack-mcp/server/router.ts @@ -366,7 +366,7 @@ async function processConnectionEventAsync( `[Router] [${traceId}] Connection: ${connectionConfig.connectionId}, Team: ${connectionConfig.teamId}`, ); console.log( - `[Router] [${traceId}] Has files: ${!!payload.event.files}, File count: ${payload.event.files?.length ?? 0}`, + `[Router] [${traceId}] Has files: ${!!(payload.event as any).files}, File count: ${(payload.event as any).files?.length ?? 0}`, ); console.log( `[Router] [${traceId}] Bot user ID from config: ${connectionConfig.botUserId ?? "not set"}`, diff --git a/slack-mcp/server/slack/handlers/eventHandler.ts b/slack-mcp/server/slack/handlers/eventHandler.ts index 115f6001..4abed1d0 100644 --- a/slack-mcp/server/slack/handlers/eventHandler.ts +++ b/slack-mcp/server/slack/handlers/eventHandler.ts @@ -482,7 +482,7 @@ export async function handleSlackEvent( `[EventHandler] Text preview: "${(payload.text ?? "").substring(0, 150)}"`, ); console.log( - `[EventHandler] Has files: ${!!payload.files}, Channel type: ${payload.channel_type ?? "unknown"}`, + `[EventHandler] Has files: ${!!(payload as any).files}, Channel type: ${(payload as any).channel_type ?? "unknown"}`, ); console.log( `[EventHandler] Bot user ID (global): ${globalBotUserId ?? "not set"}, Bot user ID (team): ${teamConfig.botUserId ?? "not set"}`, From a634f23a8f4569f96f8e2505adc4cc54b2873de1 Mon Sep 17 00:00:00 2001 From: gimenes Date: Mon, 23 Mar 2026 11:03:31 -0300 Subject: [PATCH 4/5] fix: remove raw content from debug logs, log only metadata Address PR review: replace all text previews, response previews, and raw object dumps with metadata-only logging (lengths, counts, IDs, timing). Prevents leaking user content, LLM responses, and auth tokens into production logs. Co-Authored-By: Claude Opus 4.6 (1M context) --- openrouter/server/tools/embeddings/generate.ts | 17 +++++++++++++++-- shared/mesh-chat/client.ts | 2 +- shared/mesh-chat/generate.ts | 4 ---- slack-mcp/server/lib/llm.ts | 4 ---- slack-mcp/server/lib/slack-client.ts | 4 ---- slack-mcp/server/router.ts | 4 ++-- .../server/slack/handlers/context-builder.ts | 2 +- slack-mcp/server/slack/handlers/eventHandler.ts | 14 ++++++-------- slack-mcp/server/slack/handlers/llm-handler.ts | 5 ----- 9 files changed, 25 insertions(+), 31 deletions(-) diff --git a/openrouter/server/tools/embeddings/generate.ts b/openrouter/server/tools/embeddings/generate.ts index be661f0e..660a0352 100644 --- a/openrouter/server/tools/embeddings/generate.ts +++ b/openrouter/server/tools/embeddings/generate.ts @@ -50,7 +50,17 @@ export const createGenerateEmbeddingsTool = (env: Env) => const { input, model, encoding_format, dimensions } = context; const sdk = new OpenRouter({ apiKey: getOpenRouterApiKey(env) }); - console.log({ input, model, encoding_format, dimensions }); + const inputCount = Array.isArray(input) ? input.length : 1; + const inputLength = Array.isArray(input) + ? input.reduce((sum: number, s: string) => sum + s.length, 0) + : input.length; + console.log({ + inputCount, + inputLength, + model, + encoding_format, + dimensions, + }); const result = await sdk.embeddings.generate({ input: input, model: model, @@ -58,7 +68,10 @@ export const createGenerateEmbeddingsTool = (env: Env) => dimensions: dimensions, }); - console.log({ result }); + const embeddingsCount = Array.isArray(result.data) + ? result.data.length + : 0; + console.log({ embeddingsCount, model: result.model }); return { data: result, diff --git a/shared/mesh-chat/client.ts b/shared/mesh-chat/client.ts index d9edf9e7..7a822064 100644 --- a/shared/mesh-chat/client.ts +++ b/shared/mesh-chat/client.ts @@ -123,7 +123,7 @@ export async function callDecopilotAPI( if (!response.ok) { const errorText = await response.text(); console.error( - `[MeshChat] Decopilot API FAILED: status=${response.status}, body=${errorText.substring(0, 500)}`, + `[MeshChat] Decopilot API FAILED: status=${response.status}, body length=${errorText.length}`, ); throw new Error( `Decopilot API call failed (${response.status}): ${errorText}`, diff --git a/shared/mesh-chat/generate.ts b/shared/mesh-chat/generate.ts index f7cf9ec0..08bd9c76 100644 --- a/shared/mesh-chat/generate.ts +++ b/shared/mesh-chat/generate.ts @@ -117,10 +117,8 @@ export async function generateResponse( meshUrl: config.meshUrl, }); - console.log({ config, apiMessages }); const startTime = Date.now(); const response = await callDecopilotAPI(config, apiMessages); - console.log({ response }); if (!response.body) { console.error("[MeshChat] No response body from Decopilot API!"); @@ -133,8 +131,6 @@ export async function generateResponse( console.log( `[MeshChat] Response fully collected in ${Date.now() - startTime}ms (${text.length} chars)`, ); - console.log(`[MeshChat] Response preview: "${text.substring(0, 300)}"`); - return text || "Desculpe, não consegui gerar uma resposta."; } diff --git a/slack-mcp/server/lib/llm.ts b/slack-mcp/server/lib/llm.ts index 8a1d5999..ccd5418b 100644 --- a/slack-mcp/server/lib/llm.ts +++ b/slack-mcp/server/lib/llm.ts @@ -71,7 +71,6 @@ export async function generateLLMResponse( console.log( `[LLM] Response received in ${Date.now() - startTime}ms. Length: ${result.length} chars`, ); - console.log(`[LLM] Response preview: "${result.substring(0, 300)}"`); return result; } catch (error) { console.error( @@ -118,9 +117,6 @@ export async function generateLLMResponseWithStreaming( console.log( `[LLM] Streaming complete. Total chunks: ${chunkCount}, final length: ${text.length} chars, time: ${Date.now() - startTime}ms`, ); - console.log( - `[LLM] Streaming response preview: "${text.substring(0, 300)}"`, - ); } onStream(text, isComplete); }, diff --git a/slack-mcp/server/lib/slack-client.ts b/slack-mcp/server/lib/slack-client.ts index 8588e378..3905b118 100644 --- a/slack-mcp/server/lib/slack-client.ts +++ b/slack-mcp/server/lib/slack-client.ts @@ -234,10 +234,6 @@ export async function sendMessage( console.log( `[Slack] sendMessage: channel=${options.channel}, threadTs=${options.threadTs ?? "none"}, text length=${options.text?.length ?? 0}, has blocks=${!!options.blocks}`, ); - console.log( - `[Slack] sendMessage text preview: "${(options.text ?? "").substring(0, 150)}"`, - ); - try { const startTime = Date.now(); const result = await webClient.chat.postMessage({ diff --git a/slack-mcp/server/router.ts b/slack-mcp/server/router.ts index 037fdc1b..632a31c3 100644 --- a/slack-mcp/server/router.ts +++ b/slack-mcp/server/router.ts @@ -360,7 +360,7 @@ async function processConnectionEventAsync( `[Router] [${traceId}] Thread TS: ${payload.event.thread_ts ?? "none"}`, ); console.log( - `[Router] [${traceId}] Text: ${(payload.event.text ?? "").substring(0, 200)}`, + `[Router] [${traceId}] Text length: ${(payload.event.text ?? "").length}`, ); console.log( `[Router] [${traceId}] Connection: ${connectionConfig.connectionId}, Team: ${connectionConfig.teamId}`, @@ -434,7 +434,7 @@ async function processConnectionEventAsync( cleanText = removeBotMention(cleanText, botUserId); } console.log( - `[Router] [${traceId}] Event is app_mention, will process. Clean text: "${cleanText.substring(0, 100)}"`, + `[Router] [${traceId}] Event is app_mention, will process. Clean text length: ${cleanText.length}`, ); } else if (eventType === "message") { const isDM = event.channel?.startsWith("D"); diff --git a/slack-mcp/server/slack/handlers/context-builder.ts b/slack-mcp/server/slack/handlers/context-builder.ts index 556bfa6a..1b8fc169 100644 --- a/slack-mcp/server/slack/handlers/context-builder.ts +++ b/slack-mcp/server/slack/handlers/context-builder.ts @@ -165,7 +165,7 @@ export async function buildContextMessages( ); roleMessages.forEach((msg, i) => { console.log( - `[ContextBuilder] [${i}] ${msg.role}: "${msg.content.substring(0, 80)}${msg.content.length > 80 ? "..." : ""}"`, + `[ContextBuilder] [${i}] ${msg.role}: content length=${msg.content.length}`, ); }); diff --git a/slack-mcp/server/slack/handlers/eventHandler.ts b/slack-mcp/server/slack/handlers/eventHandler.ts index 4abed1d0..4e81d292 100644 --- a/slack-mcp/server/slack/handlers/eventHandler.ts +++ b/slack-mcp/server/slack/handlers/eventHandler.ts @@ -478,9 +478,7 @@ export async function handleSlackEvent( console.log( `[EventHandler] Thread TS: ${payload.thread_ts ?? "none"}, Team: ${teamConfig.teamId}`, ); - console.log( - `[EventHandler] Text preview: "${(payload.text ?? "").substring(0, 150)}"`, - ); + console.log(`[EventHandler] Text length: ${(payload.text ?? "").length}`); console.log( `[EventHandler] Has files: ${!!(payload as any).files}, Channel type: ${(payload as any).channel_type ?? "unknown"}`, ); @@ -525,7 +523,7 @@ async function handleAppMention( `[EventHandler] From user: ${user}, Channel: ${channel}, TS: ${ts}`, ); console.log(`[EventHandler] Thread TS: ${thread_ts ?? "new thread"}`); - console.log(`[EventHandler] Full text: "${text}"`); + console.log(`[EventHandler] Text length: ${text.length}`); console.log(`[EventHandler] Files attached: ${files?.length ?? 0}`); // Check if we're in "show only final response" mode @@ -617,7 +615,7 @@ async function handleAppMention( ); messages.forEach((msg, i) => { console.log( - `[EventHandler] Message[${i}]: role=${msg.role}, content length=${msg.content.length}, has images=${!!msg.images}, content preview="${msg.content.substring(0, 100)}"`, + `[EventHandler] Message[${i}]: role=${msg.role}, content length=${msg.content.length}, has images=${!!msg.images}`, ); }); @@ -679,7 +677,7 @@ async function handleMessage( console.log( `[EventHandler] isDM: ${isDM}, channel_type: ${channel_type ?? "unknown"}, thread_ts: ${thread_ts ?? "none"}`, ); - console.log(`[EventHandler] Text: "${(text ?? "").substring(0, 200)}"`); + console.log(`[EventHandler] Text length: ${(text ?? "").length}`); console.log( `[EventHandler] Files: ${files?.length ?? 0}, botUserId: ${botUserId ?? "not set"}`, ); @@ -819,7 +817,7 @@ async function handleDirectMessage( console.log( `[EventHandler] DM from user: ${user}, Channel: ${channel}, TS: ${ts}`, ); - console.log(`[EventHandler] Text: "${text.substring(0, 200)}"`); + console.log(`[EventHandler] Text length: ${text.length}`); console.log(`[EventHandler] Media count: ${media.length}`); // Check if we're in "show only final response" mode @@ -924,7 +922,7 @@ async function handleThreadReply( console.log( `[EventHandler] Thread reply from user: ${user}, Channel: ${channel}, TS: ${ts}, ThreadTS: ${threadTs}`, ); - console.log(`[EventHandler] Text: "${text.substring(0, 200)}"`); + console.log(`[EventHandler] Text length: ${text.length}`); console.log(`[EventHandler] Media count: ${media.length}`); // Check if we're in "show only final response" mode diff --git a/slack-mcp/server/slack/handlers/llm-handler.ts b/slack-mcp/server/slack/handlers/llm-handler.ts index 9366e892..3e0fdf4c 100644 --- a/slack-mcp/server/slack/handlers/llm-handler.ts +++ b/slack-mcp/server/slack/handlers/llm-handler.ts @@ -161,10 +161,6 @@ export async function callLLMWithStreaming( console.log( `[LLMHandler] Streaming complete. Response length: ${text.length} chars, time: ${Date.now() - streamStartTime}ms`, ); - console.log( - `[LLMHandler] Response preview: "${text.substring(0, 200)}"`, - ); - const formattedText = formatForSlack(text); console.log( `[LLMHandler] Formatted text length: ${formattedText.length}, using blocks: ${useBlocks && text.length > 500}`, @@ -216,7 +212,6 @@ export async function callLLMWithoutStreaming( console.log( `[LLMHandler] Non-streaming LLM response received in ${Date.now() - nonStreamStartTime}ms. Length: ${response.length} chars`, ); - console.log(`[LLMHandler] Response preview: "${response.substring(0, 200)}"`); const formattedResponse = formatForSlack(response); console.log( `[LLMHandler] Formatted response length: ${formattedResponse.length}`, From 241d583971be4489568d0283056b77ff2cc326cd Mon Sep 17 00:00:00 2001 From: gimenes Date: Mon, 23 Mar 2026 11:04:39 -0300 Subject: [PATCH 5/5] fix(openrouter): cast embeddings result to any for metadata logging CreateEmbeddingsResponse type doesn't expose .data/.model properties directly. Cast to any since this is debug-only metadata logging. Co-Authored-By: Claude Opus 4.6 (1M context) --- openrouter/server/tools/embeddings/generate.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openrouter/server/tools/embeddings/generate.ts b/openrouter/server/tools/embeddings/generate.ts index 660a0352..4e3e9c25 100644 --- a/openrouter/server/tools/embeddings/generate.ts +++ b/openrouter/server/tools/embeddings/generate.ts @@ -68,10 +68,11 @@ export const createGenerateEmbeddingsTool = (env: Env) => dimensions: dimensions, }); - const embeddingsCount = Array.isArray(result.data) - ? result.data.length + const resultAny = result as any; + const embeddingsCount = Array.isArray(resultAny.data) + ? resultAny.data.length : 0; - console.log({ embeddingsCount, model: result.model }); + console.log({ embeddingsCount, model: resultAny.model }); return { data: result,