import { ChatInputCommandInteraction, ChannelType, SlashCommandBuilder, Guild, } from "discord.js"; import * as path from "path"; import { DisclawDatabase } from "../db/database"; import { DisclawConfig } from "../config/loader"; import { setupAgentWorkspace } from "../agent/identity"; // Allowed characters for agent names: lowercase letters, numbers, hyphens const NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,30}[a-z0-9]$/; /** * Validates that an agent name is safe and does not escape the workspaces root * via path traversal. Throws an Error if the resolved path would lie outside root. */ export function validateAgentName(name: string, workspacesRoot: string): void { if (!name) { throw new Error(`Ungültiger Agent-Name: "${name}"`); } const root = path.resolve(workspacesRoot); const wsPath = path.resolve(root, name); if (!wsPath.startsWith(root + path.sep)) { throw new Error(`Ungültiger Agent-Name: "${name}"`); } } export const newAgentCommand = new SlashCommandBuilder() .setName("new-agent") .setDescription("Erstellt einen neuen Agenten mit eigenem Kanal und Workspace") .addStringOption((option) => option .setName("name") .setDescription("Eindeutiger Name fuer den Agenten (z.B. frontend-dev)") .setRequired(true) ) .addStringOption((option) => option .setName("role") .setDescription("Rolle des Agenten (z.B. Frontend-Entwickler)") .setRequired(true) ); export async function handleNewAgent( interaction: ChatInputCommandInteraction, db: DisclawDatabase, config: DisclawConfig ): Promise { const name = interaction.options.getString("name", true).toLowerCase(); const role = interaction.options.getString("role", true); const guild = interaction.guild as Guild; // Validate name format if (!NAME_PATTERN.test(name)) { await interaction.reply({ content: "Ungueltiger Name. Erlaubt sind Kleinbuchstaben, Zahlen und Bindestriche. " + "Mindestens 2 Zeichen, muss mit Buchstabe oder Zahl beginnen und enden.", ephemeral: true, }); return; } // Path-traversal containment check try { validateAgentName(name, config.workspaces_root); } catch (error) { const msg = error instanceof Error ? error.message : "Ungültiger Agent-Name"; await interaction.reply({ content: msg, ephemeral: true }); return; } // Check uniqueness if (db.getWorkspaceByName(name)) { await interaction.reply({ content: `Ein Agent mit dem Namen **${name}** existiert bereits.`, ephemeral: true, }); return; } await interaction.deferReply(); try { // 1. Create Discord channel const channel = await guild.channels.create({ name, type: ChannelType.GuildText, topic: `Agent: ${name} | Rolle: ${role}`, }); // 2. Create workspace with full Claude Code environment const workspacePath = path.resolve(config.workspaces_root, name); setupAgentWorkspace(workspacePath, name, role, channel.id); // 3. Save to database db.createWorkspace(channel.id, guild.id, name, workspacePath); // 4. Reply with confirmation await interaction.editReply( `Agent **${name}** erstellt in <#${channel.id}>.\n` + `Rolle: ${role}\n` + `Workspace: \`${workspacePath}\`` ); // 5. Send welcome message in the new channel await channel.send( `Hallo! Ich bin **${name}** (${role}).\n` + `Schreib mir eine Nachricht und ich antworte dir.` ); } catch (error) { const msg = error instanceof Error ? error.message : "Unbekannter Fehler"; await interaction.editReply(`Fehler beim Erstellen des Agenten: ${msg}`); } }