feature/pi-refactor
  1#!/usr/bin/env node
  2/**
  3 * Daneel - XMPP Research Bot (Pi Edition)
  4 * Main entry point using Pi libraries
  5 */
  6
  7import { XmppClient } from "./xmpp/client.js";
  8import { XmppAgent } from "./pi/agent-wrapper.js";
  9import { getDefaultModel } from "./pi/config.js";
 10import { bareJid } from "./xmpp/types.js";
 11import { statusTool } from "./pi/tools/status.js";
 12import { webSearchTool } from "./pi/tools/websearch.js";
 13import { researchTool } from "./pi/tools/research.js";
 14import { withLogging } from "./pi/tools/logging.js";
 15import * as os from "os";
 16
 17interface Config {
 18  xmpp: {
 19    jid: string;
 20    password: string;
 21    ownerJid: string;
 22  };
 23  paths: {
 24    dataDir: string;
 25    inboxPath: string;
 26  };
 27  debug: boolean;
 28}
 29
 30function loadConfig(): Config {
 31  const jid = process.env.DANEEL_XMPP_JID;
 32  const password = process.env.DANEEL_XMPP_PASSWORD;
 33  const ownerJid = process.env.DANEEL_OWNER_JID;
 34
 35  if (!jid || !password || !ownerJid) {
 36    console.error("Missing required environment variables:");
 37    if (!jid) console.error("  DANEEL_XMPP_JID");
 38    if (!password) console.error("  DANEEL_XMPP_PASSWORD");
 39    if (!ownerJid) console.error("  DANEEL_OWNER_JID");
 40    process.exit(1);
 41  }
 42
 43  const dataDir = process.env.DANEEL_DATA_DIR || "./data";
 44  const inboxPath = process.env.DANEEL_INBOX_PATH || 
 45    `${os.homedir()}/desktop/org/inbox.org`;
 46  const debug = process.env.DANEEL_DEBUG === "true";
 47
 48  return {
 49    xmpp: { jid, password, ownerJid },
 50    paths: { dataDir, inboxPath },
 51    debug,
 52  };
 53}
 54
 55async function main() {
 56  console.log("Daneel - XMPP Research Bot");
 57  console.log("===========================\n");
 58
 59  const config = loadConfig();
 60  
 61  // Get default model
 62  const defaultModel = getDefaultModel();
 63  console.log(`Default model: ${defaultModel.provider}/${defaultModel.id}`);
 64
 65  // Available tools (wrap with logging if debug enabled)
 66  const rawTools = [statusTool, webSearchTool, researchTool];
 67  const tools = rawTools.map(tool => withLogging(tool, config.debug));
 68  console.log(`Tools available: ${rawTools.map(t => t.name).join(", ")}`);
 69
 70  // Map of JID -> XmppAgent
 71  const agents = new Map<string, XmppAgent>();
 72
 73  function getOrCreateAgent(jid: string): XmppAgent {
 74    const bare = bareJid(jid);
 75    let agent = agents.get(bare);
 76    
 77    if (!agent) {
 78      if (config.debug) {
 79        console.log(`[Main] Creating new agent for JID: ${bare}`);
 80      }
 81      agent = new XmppAgent(bare, defaultModel, tools, config.debug);
 82      agents.set(bare, agent);
 83    }
 84    
 85    return agent;
 86  }
 87
 88  // Create XMPP client
 89  const xmpp = new XmppClient(config.xmpp);
 90
 91  // Set up message handler
 92  xmpp.onMessage(async (message) => {
 93    const timestamp = new Date().toISOString();
 94    const from = bareJid(message.from);
 95    const preview = message.body.slice(0, 50);
 96    const requestStartTime = Date.now();
 97    
 98    console.log(`[${timestamp}] ← Message from ${from}: ${preview}${message.body.length > 50 ? "..." : ""}`);
 99    
100    try {
101      const agent = getOrCreateAgent(message.from);
102      const response = await agent.processMessage(message.body);
103      
104      await xmpp.sendMessage(message.from, response);
105      
106      const elapsed = Date.now() - requestStartTime;
107      const timestamp2 = new Date().toISOString();
108      
109      if (config.debug) {
110        console.log(`[${timestamp2}] → Response sent to ${from} (${response.length} chars, ${elapsed}ms total)`);
111      } else {
112        console.log(`[${timestamp2}] → Response sent to ${from} (${elapsed}ms)`);
113      }
114    } catch (error) {
115      const elapsed = Date.now() - requestStartTime;
116      console.error(`[${timestamp}] ✗ Error after ${elapsed}ms:`, error);
117      
118      // Check if it's an API key error
119      const errorMsg = error instanceof Error ? error.message : "Unknown error";
120      
121      let userMessage = `Sorry, I encountered an error: ${errorMsg}`;
122      
123      if (errorMsg.includes("No API key for provider")) {
124        const provider = errorMsg.match(/provider: (\w+)/)?.[1];
125        userMessage = `I tried to use ${provider} but don't have an API key configured. I'm working with limited capabilities right now. Try asking simpler questions or use slash commands like /status.`;
126      }
127      
128      try {
129        await xmpp.sendMessage(message.from, userMessage);
130        console.log(`[${timestamp}] → Error message sent to ${from}`);
131      } catch (sendError) {
132        console.error(`[${timestamp}] ✗ Failed to send error message:`, sendError);
133      }
134    }
135  });
136
137  // Graceful shutdown
138  const shutdown = async (signal: string) => {
139    console.log(`\nReceived ${signal}, shutting down gracefully...`);
140    
141    // TODO: Save sessions to disk (Phase 9)
142    
143    await xmpp.stop();
144    console.log("XMPP client stopped");
145    
146    process.exit(0);
147  };
148
149  process.on("SIGINT", () => shutdown("SIGINT"));
150  process.on("SIGTERM", () => shutdown("SIGTERM"));
151
152  // Start XMPP client
153  console.log("\nConfiguration:");
154  console.log(`  Bot JID:     ${config.xmpp.jid}`);
155  console.log(`  Owner JID:   ${config.xmpp.ownerJid}`);
156  console.log(`  Data dir:    ${config.paths.dataDir}`);
157  console.log(`  Inbox path:  ${config.paths.inboxPath}`);
158  console.log(`  Debug mode:  ${config.debug}`);
159  console.log();
160  console.log("Connecting to XMPP server...");
161  
162  await xmpp.start();
163  
164  console.log("Connected! Waiting for messages...\n");
165}
166
167// Run main function
168main().catch((error) => {
169  console.error("Fatal error:", error);
170  process.exit(1);
171});