- Add deleteWorkspaceByChannelId() to DisclawDatabase - Add src/lifecycle/categories.ts: ensureCategories() + moveToCategory() - Add src/lifecycle/reconcile.ts: reconcile() assigns channels to active/inactive/archive categories on startup - Add src/lifecycle/channel-delete-handler.ts: handles channelDelete events for management, category, and agent channels - Wire lifecycle into bot.ts: state object, ensureCategories, reconcile, channelDelete listener - Extend handleNewAgent() with optional activeCategoryId to place new channels in the active category - Add 13 unit tests covering all lifecycle scenarios Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
83 lines
2.9 KiB
TypeScript
83 lines
2.9 KiB
TypeScript
import { Channel, ChannelType, Guild } from "discord.js";
|
|
import * as fs from "node:fs";
|
|
import { DisclawDatabase } from "../db/database.js";
|
|
import { DisclawConfig } from "../config/loader.js";
|
|
import { ResolvedCategories, ensureCategories } from "./categories.js";
|
|
import { childLogger } from "../runtime/logger.js";
|
|
|
|
const log = childLogger("lifecycle:channel-delete");
|
|
|
|
export interface ChannelDeleteContext {
|
|
db: DisclawDatabase;
|
|
config: DisclawConfig;
|
|
guild: Guild;
|
|
categories: ResolvedCategories;
|
|
managementChannelId: string;
|
|
setManagementChannelId: (id: string) => void;
|
|
setCategories: (cats: ResolvedCategories) => void;
|
|
}
|
|
|
|
export async function handleChannelDelete(
|
|
channel: Channel,
|
|
ctx: ChannelDeleteContext
|
|
): Promise<void> {
|
|
const channelId = channel.id;
|
|
|
|
// 1. Was the management channel deleted?
|
|
if (channelId === ctx.managementChannelId) {
|
|
log.warn({ channelId }, "Management channel was deleted — recreating");
|
|
try {
|
|
const newChannel = await ctx.guild.channels.create({
|
|
name: ctx.config.management_channel,
|
|
type: ChannelType.GuildText,
|
|
topic: "DisClaw Management -- verwende /new-agent um Agenten zu erstellen",
|
|
});
|
|
ctx.setManagementChannelId(newChannel.id);
|
|
// Update DB: remove old entry, add new one
|
|
ctx.db.deleteWorkspaceByChannelId(channelId);
|
|
ctx.db.createWorkspace(newChannel.id, ctx.guild.id, ctx.config.management_channel, "__management__");
|
|
log.info({ newChannelId: newChannel.id }, "Management channel recreated");
|
|
} catch (err) {
|
|
log.error({ err }, "Failed to recreate management channel");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 2. Was one of the lifecycle category channels deleted?
|
|
const isCategoryChannel =
|
|
channelId === ctx.categories.active.id ||
|
|
channelId === ctx.categories.inactive.id ||
|
|
channelId === ctx.categories.archive.id;
|
|
|
|
if (isCategoryChannel) {
|
|
log.warn({ channelId }, "Lifecycle category channel was deleted — re-ensuring categories");
|
|
try {
|
|
const newCats = await ensureCategories(ctx.guild);
|
|
ctx.setCategories(newCats);
|
|
log.info("Categories re-ensured");
|
|
} catch (err) {
|
|
log.error({ err }, "Failed to re-ensure categories");
|
|
}
|
|
return;
|
|
}
|
|
|
|
// 3. Was an agent channel deleted?
|
|
const workspace = ctx.db.getWorkspaceByChannelId(channelId);
|
|
if (workspace) {
|
|
const workspacePath = workspace.workspace_path;
|
|
|
|
// Delete workspace directory before removing from DB
|
|
if (workspacePath && workspacePath !== "__management__") {
|
|
try {
|
|
fs.rmSync(workspacePath, { recursive: true, force: true });
|
|
log.info({ workspacePath }, "Agent workspace directory deleted");
|
|
} catch (err) {
|
|
log.warn({ workspacePath, err }, "Failed to delete agent workspace directory");
|
|
}
|
|
}
|
|
|
|
// Remove from DB
|
|
ctx.db.deleteWorkspaceByChannelId(channelId);
|
|
log.info({ channelId, agentName: workspace.agent_name }, "Agent channel and workspace removed from DB");
|
|
}
|
|
}
|