UNPKG

@codemafia0000/d0

Version:

Claude Multi-Agent Automated Development AI - Revolutionary development environment where multiple AI agents collaborate to automate software development

436 lines (357 loc) • 16.3 kB
const fs = require('fs'); const path = require('path'); const chalk = require('chalk'); const CONSTANTS = require('../constants'); const { getHistoryDir } = require('../history/session-history'); function sendMessageToAgent(recipient, message, numWorkers = 3) { const timestamp = new Date().toISOString(); // Log the message to .d0/history const historyDir = getHistoryDir(); const logMessage = `[${timestamp}] TO ${recipient}: ${message}`; const logFile = path.join(historyDir, 'agent_messages.log'); try { fs.appendFileSync(logFile, logMessage + '\n'); } catch (error) { console.warn(chalk.yellow(`Warning: Could not log message: ${error.message}`)); } // Check if sessions are active const sessionFile = CONSTANTS.SESSIONS_FILE; if (!fs.existsSync(sessionFile)) { console.error(chalk.red('āŒ No active sessions found. Run "d0 start" first.')); return false; } // Validate recipient const sessionInfo = JSON.parse(fs.readFileSync(sessionFile, 'utf8')); if (!sessionInfo.sessions.includes(recipient.toLowerCase())) { console.error(chalk.red(`āŒ Unknown recipient: ${recipient}`)); console.log(chalk.yellow('Available recipients:'), sessionInfo.sessions.join(', ')); return false; } // For Node.js version, we simulate message delivery const displayName = recipient.toUpperCase(); try { console.log(chalk.blue(`šŸ“¤ Sending to ${displayName}...`)); // Write message to agent's inbox for them to see when they connect const agentInboxDir = CONSTANTS.INBOX_DIR; if (!fs.existsSync(agentInboxDir)) { fs.mkdirSync(agentInboxDir, { recursive: true }); } const inboxFile = path.join(agentInboxDir, `${recipient.toLowerCase()}.txt`); const messageEntry = `[${timestamp}] ${message}\n`; fs.appendFileSync(inboxFile, messageEntry); // Create notification marker for agent communication const notificationFile = path.join(agentInboxDir, `${recipient.toLowerCase()}_notification.txt`); fs.writeFileSync(notificationFile, timestamp); // Auto-process message after 3 seconds (simulate agent reading) setTimeout(() => { autoProcessMessage(recipient, messageEntry); }, 3000); // Create communication status tracking const statusFile = CONSTANTS.MESSAGE_STATUS_FILE; let messageStatus = {}; if (fs.existsSync(statusFile)) { try { messageStatus = JSON.parse(fs.readFileSync(statusFile, 'utf8')); } catch (e) { messageStatus = {}; } } const messageId = `${timestamp}_${recipient}`; messageStatus[messageId] = { recipient, message: message.substring(0, CONSTANTS.MESSAGE_PREVIEW_LENGTH) + (message.length > CONSTANTS.MESSAGE_PREVIEW_LENGTH ? '...' : ''), timestamp, status: 'sent', delivered: false }; fs.writeFileSync(statusFile, JSON.stringify(messageStatus, null, 2)); console.log(chalk.green(`āœ… Message sent to ${displayName}`)); console.log(chalk.gray(`šŸ¤– Agent will auto-process message within 5 seconds`)); // Show enhanced communication tips for different delegation paths if (recipient.toLowerCase() === 'boss') { console.log(chalk.yellow('\nšŸ’” Communication Tips:')); console.log(chalk.gray(' • Boss will delegate tasks to workers automatically')); console.log(chalk.gray(' • Use specific deliverables and deadlines for best results')); console.log(chalk.gray(' • Boss will report progress back to you')); } else if (recipient.toLowerCase().startsWith('worker')) { console.log(chalk.yellow('\nšŸ’” Worker Communication Tips:')); console.log(chalk.gray(' • Include specific task requirements and deadlines')); console.log(chalk.gray(' • Worker will report progress every 30 minutes')); console.log(chalk.gray(' • Specify working directory and deliverable files')); console.log(chalk.gray(' • Worker will notify boss upon completion')); } return true; } catch (error) { console.error(chalk.red(`āŒ Failed to send to ${displayName}: ${error.message}`)); return false; } } function checkCommunicationStatus() { const inboxDir = CONSTANTS.INBOX_DIR; if (!fs.existsSync(inboxDir)) { return { roles: [], hasMessages: false }; } const roles = CONSTANTS.AGENT_ROLES; const statusMap = {}; let hasMessages = false; for (const role of roles) { const inboxFile = path.join(inboxDir, `${role}.txt`); const notificationFile = path.join(inboxDir, `${role}_notification.txt`); if (fs.existsSync(inboxFile)) { const messages = fs.readFileSync(inboxFile, 'utf8').trim().split('\n').filter(line => line.trim()); const hasNotification = fs.existsSync(notificationFile); statusMap[role] = { messageCount: messages.length, hasUnread: hasNotification }; if (hasNotification) hasMessages = true; } else { statusMap[role] = { messageCount: 0, hasUnread: false }; } } return { roles: statusMap, hasMessages }; } function autoProcessMessage(recipient, messageEntry) { try { // Extract message content const messageContent = messageEntry.replace(/^\[.*?\]\s*/, '').trim(); console.log(chalk.cyan(`šŸ¤– ${recipient.toUpperCase()} auto-processing: "${messageContent.substring(0, 50)}..."`)); // Remove notification marker to mark as read const notificationFile = CONSTANTS.getInboxPath(`${recipient.toLowerCase()}_notification.txt`); if (fs.existsSync(notificationFile)) { fs.unlinkSync(notificationFile); } // Update message status const statusFile = CONSTANTS.MESSAGE_STATUS_FILE; if (fs.existsSync(statusFile)) { try { const messageStatus = JSON.parse(fs.readFileSync(statusFile, 'utf8')); // Update the latest message for this agent for (const [messageId, msgData] of Object.entries(messageStatus)) { if (msgData.recipient === recipient) { msgData.status = 'delivered'; msgData.delivered = true; msgData.processedAt = new Date().toISOString(); break; } } fs.writeFileSync(statusFile, JSON.stringify(messageStatus, null, 2)); } catch (error) { console.warn(chalk.yellow('Warning: Could not update message status')); } } // Create agent response log const historyDir = getHistoryDir(); if (fs.existsSync(historyDir)) { const responseFile = path.join(historyDir, 'agent_responses.log'); const timestamp = new Date().toISOString(); const responseEntry = `[${timestamp}] ${recipient.toUpperCase()}: Acknowledged "${messageContent}"\n`; fs.appendFileSync(responseFile, responseEntry); } // Launch Claude CLI for the agent to start working // This is enabled by default, can be disabled with D0_AUTO_CLAUDE=false const shouldLaunchClaude = process.env.D0_AUTO_CLAUDE !== 'false'; if (shouldLaunchClaude && (recipient.startsWith('worker') || recipient === 'boss' || recipient === 'president')) { setTimeout(() => { launchClaudeForAgent(recipient, messageContent); }, 1000); } // Implement delegation chain: president -> boss -> worker1 if (recipient.toLowerCase() === 'president' && messageContent.length > 10) { setTimeout(() => { console.log(chalk.blue('šŸ‘‘ PRESIDENT delegating to BOSS...')); const delegatedMessage = `Strategic task from PRESIDENT: ${messageContent}\n\nPlease coordinate the team to execute this vision.`; sendMessageToAgent('boss', delegatedMessage, 3); }, 2000); } else if (recipient.toLowerCase() === 'boss' && messageContent.length > 10) { setTimeout(() => { console.log(chalk.blue('šŸŽÆ BOSS will handle task delegation according to team expertise...')); // NOTE: BOSS should delegate tasks based on instructions/boss.md guidelines // Task delegation logic is now managed by the BOSS agent according to their instructions }, 2000); } } catch (error) { console.error(chalk.red(`Error in auto-processing for ${recipient}:`), error.message); } } function launchClaudeForAgent(agent, messageContent) { const { spawn } = require('child_process'); const fs = require('fs'); const path = require('path'); const { setClaudeEnvironment } = require('../utils/claude-config'); try { console.log(chalk.magenta(`šŸš€ Preparing context for ${agent.toUpperCase()}...`)); // Load agent-specific instructions from .md files const instructionFile = getInstructionFile(agent); let instructions = ''; if (fs.existsSync(instructionFile)) { instructions = fs.readFileSync(instructionFile, 'utf8'); console.log(chalk.gray(`šŸ“š Loaded instructions from: ${path.basename(instructionFile)}`)); } else { console.log(chalk.yellow(`āš ļø Instruction file not found: ${instructionFile}`)); instructions = `You are ${agent.toUpperCase()}, a development team member.`; } // Load CLAUDE.md for communication system info const claudeFile = path.join(CONSTANTS.PROJECT_ROOT, 'CLAUDE.md'); let claudeInstructions = ''; if (fs.existsSync(claudeFile)) { claudeInstructions = fs.readFileSync(claudeFile, 'utf8'); console.log(chalk.gray(`šŸ“‹ Loaded CLAUDE.md communication guidelines`)); } // Create comprehensive context message with instructions const contextMessage = `${instructions} ## Current Task ${messageContent} ## Communication System ${claudeInstructions} ## Important Reminders 1. Check for messages first: look for files in .d0/tmp/inbox/${agent}.txt 2. Use d0 commands for communication: - d0 tell boss "message" - Report to boss - d0 tell president "message" - Report to president (if needed) - d0 comm - Check communication status 3. When task is complete, report with: d0 tell boss "Task completed: [description of what you accomplished]" 4. Follow your role-specific instructions above for task delegation, progress reporting, and communication patterns. Start working now according to your instructions!`; // Create a temporary context file and launch Claude CLI with it const contextDir = CONSTANTS.getTmpPath('context'); if (!fs.existsSync(contextDir)) { fs.mkdirSync(contextDir, { recursive: true }); } const contextFile = path.join(contextDir, `${agent}_context.md`); fs.writeFileSync(contextFile, contextMessage); console.log(chalk.green(`āœ… Context prepared for ${agent.toUpperCase()}`)); console.log(chalk.blue(`šŸ“„ Context file: ${contextFile}`)); // Ensure Claude environment is set setClaudeEnvironment(); // Try to launch Claude CLI in auto-approve mode (non-interactive) try { console.log(chalk.magenta(`šŸš€ Launching Claude CLI for ${agent.toUpperCase()} with auto-approve...`)); const claude = spawn('claude', [contextFile, '--dangerously-skip-permissions'], { stdio: ['pipe', 'pipe', 'pipe'], cwd: CONSTANTS.PROJECT_ROOT, detached: true, env: { ...process.env } }); // Auto-send "2" to accept permissions prompt, then let it run interactively setTimeout(() => { claude.stdin.write('2\n'); }, 1000); // Handle output without blocking parent process claude.stdout.on('data', (data) => { const output = data.toString(); console.log(chalk.gray(`[${agent.toUpperCase()}] ${output.trim()}`)); // Check for specific prompts and auto-respond if (output.includes('Do you want to proceed?') || output.includes('1. No, exit') || output.includes('2. Yes, I accept')) { claude.stdin.write('2\n'); } }); claude.stderr.on('data', (data) => { console.error(chalk.red(`[${agent.toUpperCase()} ERROR] ${data.toString().trim()}`)); }); claude.on('close', (code) => { console.log(chalk.blue(`āœ… ${agent.toUpperCase()} completed (exit code: ${code})`)); }); claude.unref(); // Allow parent process to exit console.log(chalk.green(`āœ… Claude CLI launched for ${agent.toUpperCase()} in auto-approve mode`)); } catch (error) { // Try alternative approach with environment variable try { console.log(chalk.yellow(`āš ļø Skip permissions flag failed, trying with basic non-interactive mode...`)); const claude = spawn('claude', [contextFile], { stdio: ['pipe', 'pipe', 'pipe'], cwd: CONSTANTS.PROJECT_ROOT, detached: true, env: { ...process.env } }); // Auto-send "2" to accept permissions prompt setTimeout(() => { claude.stdin.write('2\n'); }, 1000); // Handle output without blocking parent process claude.stdout.on('data', (data) => { const output = data.toString(); console.log(chalk.gray(`[${agent.toUpperCase()}] ${output.trim()}`)); // Check for specific prompts and auto-respond if (output.includes('Do you want to proceed?') || output.includes('1. No, exit') || output.includes('2. Yes, I accept')) { claude.stdin.write('2\n'); } }); claude.stderr.on('data', (data) => { console.error(chalk.red(`[${agent.toUpperCase()} ERROR] ${data.toString().trim()}`)); }); claude.on('close', (code) => { console.log(chalk.blue(`āœ… ${agent.toUpperCase()} completed (exit code: ${code})`)); }); claude.unref(); console.log(chalk.green(`āœ… Claude CLI launched for ${agent.toUpperCase()} with environment flags`)); } catch (envError) { // Final fallback to manual instructions with auto-approve suggestion console.log(chalk.yellow(`āš ļø Auto-launch failed: ${error.message}`)); console.log(chalk.cyan(`\nšŸš€ Manual launch instructions for ${agent.toUpperCase()}:`)); console.log(chalk.gray(` claude -p --dangerously-skip-permissions ${contextFile}`)); console.log(chalk.gray(` or: claude -p ${contextFile}`)); } } } catch (error) { console.error(chalk.red(`Failed to prepare context for ${agent}:`), error.message); } } function getInstructionFile(agent) { const instructionsDir = CONSTANTS.getD0Path('instructions'); if (agent === 'president') { return path.join(instructionsDir, 'president.md'); } else if (agent === 'boss') { return path.join(instructionsDir, 'boss.md'); } else if (agent.startsWith('worker')) { return path.join(instructionsDir, 'worker.md'); } // Fallback return path.join(instructionsDir, 'worker.md'); } function getAgentRole(agent) { if (agent === 'president') { return 'the project president responsible for strategic oversight'; } else if (agent === 'boss') { return 'the technical manager coordinating the development team'; } else if (agent.startsWith('worker')) { const workerIndex = parseInt(agent.replace('worker', '')) - 1; const specializations = CONSTANTS.WORKER_SPECIALIZATIONS; const spec = specializations[workerIndex % specializations.length]; return `a ${spec.title} specializing in ${spec.focus}`; } return 'a development team member'; } function clearCommunicationNotifications() { const inboxDir = CONSTANTS.INBOX_DIR; if (!fs.existsSync(inboxDir)) { return 0; } const roles = CONSTANTS.AGENT_ROLES; let clearedCount = 0; for (const role of roles) { const notificationFile = path.join(inboxDir, `${role}_notification.txt`); if (fs.existsSync(notificationFile)) { try { fs.unlinkSync(notificationFile); clearedCount++; } catch (error) { console.warn(chalk.yellow(`Warning: Could not clear notification for ${role}`)); } } } return clearedCount; } module.exports = { sendMessageToAgent, checkCommunicationStatus, clearCommunicationNotifications };