disclaw/src/bot.ts
Nick Tabeling 1cafaabe2a
Some checks failed
CI / build-and-test (ubuntu-latest) (pull_request) Has been cancelled
CI / build-and-test (windows-latest) (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
feat(DIS-101): introduce pino structured logger, remove console.*
Replace all console.* calls in src/ with pino child-loggers.
Add rootLogger + childLogger(component) in src/runtime/logger.ts.
pino-pretty used as dev transport only.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 17:09:37 +02:00

154 lines
4.5 KiB
TypeScript

import {
Client,
GatewayIntentBits,
ChannelType,
REST,
Routes,
TextChannel,
Interaction,
} from "discord.js";
import { DisclawDatabase } from "./db/database";
import { DisclawConfig, EnvConfig } from "./config/loader";
import { newAgentCommand, handleNewAgent } from "./commands/new-agent";
import { routeMessage } from "./router";
import { childLogger } from "./runtime/logger";
const log = childLogger("bot");
export async function startBot(
envConfig: EnvConfig,
disclawConfig: DisclawConfig,
db: DisclawDatabase
): Promise<Client> {
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
// Track the management channel id once resolved
let managementChannelId: string | null = null;
// --- Event: Ready ---
client.once("ready", async () => {
if (!client.user) return;
log.info({ tag: client.user.tag }, "Bot online");
const guild = envConfig.DISCORD_GUILD_ID
? client.guilds.cache.get(envConfig.DISCORD_GUILD_ID)
: client.guilds.cache.first();
if (!guild) {
log.error("No guild found. Is the bot added to a server?");
process.exit(1);
}
log.info({ guildName: guild.name, guildId: guild.id }, "Connected to guild");
// --- Ensure management channel exists ---
const managementName = disclawConfig.management_channel;
let existingWorkspace = db.getWorkspaceByName(managementName);
if (existingWorkspace) {
// Verify the channel still exists on Discord
const existingChannel = guild.channels.cache.get(
existingWorkspace.channel_id
);
if (existingChannel) {
managementChannelId = existingWorkspace.channel_id;
log.info({ channelName: managementName }, "Management channel found");
} else {
// Channel was deleted from Discord, recreate it
existingWorkspace = undefined;
}
}
if (!existingWorkspace) {
// Look for an existing channel with that name first
let mgmtChannel: TextChannel;
const found = guild.channels.cache.find(
(ch) =>
ch.type === ChannelType.GuildText && ch.name === managementName
) as TextChannel | undefined;
if (found) {
db.createWorkspace(
found.id,
guild.id,
managementName,
"__management__"
);
managementChannelId = found.id;
mgmtChannel = found;
log.info({ channelName: managementName }, "Adopted existing channel");
} else {
const newChannel = await guild.channels.create({
name: managementName,
type: ChannelType.GuildText,
topic: "DisClaw Management -- verwende /new-agent um Agenten zu erstellen",
});
db.createWorkspace(
newChannel.id,
guild.id,
managementName,
"__management__"
);
managementChannelId = newChannel.id;
mgmtChannel = newChannel;
log.info({ channelName: managementName }, "Created management channel");
}
// Send welcome message using the direct channel reference
await mgmtChannel
.send(
"DisClaw ist bereit. Verwende `/new-agent` um einen neuen Agenten zu erstellen."
)
.catch(() => {});
}
// --- Register slash commands ---
const rest = new REST({ version: "10" }).setToken(
envConfig.DISCORD_BOT_TOKEN
);
try {
await rest.put(
Routes.applicationGuildCommands(client.user.id, guild.id),
{
body: [newAgentCommand.toJSON()],
}
);
log.info("Slash commands registered.");
} catch (err) {
log.error({ err }, "Failed to register slash commands");
}
});
// --- Event: interactionCreate (slash commands) ---
client.on("interactionCreate", async (interaction: Interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === "new-agent") {
await handleNewAgent(interaction, db, disclawConfig);
}
});
// --- Event: messageCreate (agent routing) ---
client.on("messageCreate", async (message) => {
// Ignore bot messages
if (message.author.bot) return;
// Only handle text channels in guilds
if (!message.guild || message.channel.type !== ChannelType.GuildText) return;
await routeMessage(message, db, disclawConfig, managementChannelId);
});
// --- Login ---
await client.login(envConfig.DISCORD_BOT_TOKEN);
return client;
}