import * as os from "os"; import { Message, TextChannel } from "discord.js"; import { DisclawDatabase } from "./db/database"; import { DisclawConfig } from "./config/loader"; import { runAgent } from "./agent/runner"; import { splitForDiscord } from "./discord/split"; import { sanitizeForDiscord } from "./runtime/sanitize"; import type { RunResult } from "./agent/types"; /** * Routes a Discord message to the correct agent based on channel_id. * Returns true if the message was handled, false if the channel is not * mapped to any agent. */ export async function routeMessage( message: Message, db: DisclawDatabase, config: DisclawConfig, managementChannelId: string | null ): Promise { const channelId = message.channelId; // Ignore messages in the management channel (commands only) if (channelId === managementChannelId) { return false; } // Look up workspace for this channel const workspace = db.getWorkspaceByChannelId(channelId); if (!workspace) { return false; } const channel = message.channel as TextChannel; // Show typing indicator while the agent works const typingInterval = setInterval(() => { channel.sendTyping().catch(() => {}); }, 5000); // Send immediately as well await channel.sendTyping().catch(() => {}); const sanitizeOpts = { repoRoot: workspace.workspace_path, home: os.homedir(), }; try { // Load recent conversation history for context const history = db.getConversationHistory(workspace.id, 30); // Run the agent const result: RunResult = await runAgent({ workspacePath: workspace.workspace_path, channelName: channel.name, userMessage: message.content, conversationHistory: history, }); if (result.status === "success") { // Store user message db.addConversation( workspace.id, message.id, "user", message.content, message.author.username ); // Sanitize the full response once, then use the sanitized version // for both Discord output and conversation history storage. // This prevents a feedback loop where raw paths in stored history // get fed back to the agent as context, causing it to reproduce them. const sanitizedResponse = sanitizeForDiscord(result.text, sanitizeOpts); // Split and send response const chunks = splitForDiscord(sanitizedResponse); let firstMsgId: string | null = null; for (const chunk of chunks) { const sent = await channel.send(chunk); if (!firstMsgId) { firstMsgId = sent.id; } } // Store sanitized assistant response (use first message id for dedup) if (firstMsgId) { db.addConversation( workspace.id, firstMsgId, "assistant", sanitizedResponse, null ); } } else if (result.status === "timeout") { await channel .send(`⏱️ Agent-Timeout nach ${result.timeoutMs / 1000}s`) .catch(() => {}); } else if (result.status === "cli-error") { await channel .send(sanitizeForDiscord(`❌ CLI-Fehler: ${result.message}`, sanitizeOpts)) .catch(() => {}); } else if (result.status === "parse-error") { await channel .send(`⚠️ Antwort konnte nicht gelesen werden`) .catch(() => {}); } } catch (error) { const msg = error instanceof Error ? error.message : "Unbekannter Fehler"; await channel .send(sanitizeForDiscord(`Fehler bei der Verarbeitung: ${msg}`, sanitizeOpts)) .catch(() => {}); } finally { clearInterval(typingInterval); } return true; }