@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
JavaScript
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
};