import IRC from "irc-framework"; import { startAgent, stopAgent, listAgents, stopAllAgents, listTemplates, reconcileAgents, reloadAgent, type AgentInfo, } from "./agent-manager.js"; interface OverseerConfig { server: string; port: number; nick: string; channel: string; } function log(msg: string) { process.stderr.write(`[overseer] ${msg}\n`); } function formatAgentList(agents: AgentInfo[]): string[] { if (agents.length === 0) return ["No agents running."]; return agents.map( (a) => `${a.name} (${a.template}) — ${a.nick} [${a.model}] ip=${a.ip} since ${a.startedAt.slice(11, 19)}` ); } export async function runOverseer(config: OverseerConfig) { // Reconcile agent state on startup log("Reconciling agent state..."); const { adopted, cleaned } = reconcileAgents(); if (adopted.length > 0) { log(`Adopted ${adopted.length} running agent(s): ${adopted.join(", ")}`); } if (cleaned.length > 0) { log(`Cleaned ${cleaned.length} dead agent(s): ${cleaned.join(", ")}`); } let knownAgents = new Set(listAgents().map((a) => a.name)); const bot = new IRC.Client(); bot.connect({ host: config.server, port: config.port, nick: config.nick, }); bot.on("registered", () => { log(`Connected to ${config.server}:${config.port} as ${config.nick}`); bot.join(config.channel); bot.join("#agents"); log(`Joined ${config.channel} and #agents`); }); bot.on("message", async (event: { nick: string; target: string; message: string }) => { // Only handle channel messages if (!event.target.startsWith("#")) return; const text = event.message.trim(); if (!text.startsWith("!")) return; const parts = text.split(/\s+/); const cmd = parts[0].toLowerCase(); try { switch (cmd) { case "!invoke": { const template = parts[1]; if (!template) { bot.say(event.target, "Usage: !invoke