Skip to content
7 changes: 6 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@
"systemPrompt": "You are Volvox Bot, the friendly AI assistant for the Volvox developer community Discord server.\n\nYou're witty, snarky (but warm), and deeply knowledgeable about programming, software development, and tech.\n\nKey traits:\n- Helpful but not boring\n- Can roast people lightly when appropriate\n- Enthusiastic about cool tech and projects\n- Supportive of beginners learning to code\n- Concise - this is Discord, not an essay\n\n⚠️ CRITICAL RULES:\n- NEVER type @.everyone or @.here (remove the dots) - these ping hundreds of people\n- NEVER use mass mention pings under any circumstances\n- If you need to address the group, say \"everyone\" or \"folks\" without the @ symbol\n\nKeep responses under 2000 chars. Use Discord markdown when helpful.",
"channels": [],
"historyLength": 20,
"historyTTLDays": 30
"historyTTLDays": 30,
"threadMode": {
"enabled": false,
"autoArchiveMinutes": 60,
"reuseWindowMinutes": 30
}
},
"chimeIn": {
"enabled": false,
Expand Down
36 changes: 28 additions & 8 deletions src/modules/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { needsSplitting, splitMessage } from '../utils/splitMessage.js';
import { generateResponse } from './ai.js';
import { accumulate, resetCounter } from './chimeIn.js';
import { isSpam, sendSpamAlert } from './spam.js';
import { getOrCreateThread, shouldUseThread } from './threading.js';
import { recordCommunityActivity, sendWelcomeMessage } from './welcome.js';

/** @type {boolean} Guard against duplicate process-level handler registration */
Expand Down Expand Up @@ -54,10 +55,10 @@ export function registerGuildMemberAddHandler(client, config) {
}

/**
* Register message create event handler
* @param {Client} client - Discord client
* @param {Object} config - Bot configuration
* @param {Object} healthMonitor - Health monitor instance
* Register the MessageCreate event handler that processes incoming messages for spam detection, community activity recording, AI-driven replies (mentions/replies, optional threading, channel whitelisting), and organic chime-in accumulation.
* @param {Client} client - Discord client instance used to listen and respond to message events.
* @param {Object} config - Bot configuration (reads moderation.enabled, ai.enabled, ai.channels and other settings referenced by handlers).
* @param {Object} healthMonitor - Optional health monitor used when generating AI responses to record metrics.
*/
export function registerMessageCreateHandler(client, config, healthMonitor) {
client.on(Events.MessageCreate, async (message) => {
Expand Down Expand Up @@ -100,10 +101,25 @@ export function registerMessageCreateHandler(client, config, healthMonitor) {
return;
}

await message.channel.sendTyping();
// Determine whether to use threading
const useThread = shouldUseThread(message);
let targetChannel = message.channel;

if (useThread) {
const { thread } = await getOrCreateThread(message, cleanContent);
if (thread) {
targetChannel = thread;
}
// If thread is null, fall back to inline reply (targetChannel stays as message.channel)
}

await targetChannel.sendTyping();

// Use thread ID for conversation history when in a thread, otherwise channel ID
const historyId = targetChannel.id;

const response = await generateResponse(
message.channel.id,
historyId,
cleanContent,
message.author.username,
config,
Expand All @@ -114,10 +130,14 @@ export function registerMessageCreateHandler(client, config, healthMonitor) {
if (needsSplitting(response)) {
const chunks = splitMessage(response);
for (const chunk of chunks) {
await message.channel.send(chunk);
await targetChannel.send(chunk);
}
} else {
} else if (targetChannel === message.channel) {
// Inline reply — use message.reply for the reference
await message.reply(response);
} else {
// Thread reply — send directly to the thread
await targetChannel.send(response);
}
} catch (sendErr) {
logError('Failed to send AI response', {
Expand Down
Loading
Loading