Skip to main content
Glama

Agent MCP

testingAgent.ts23.4 kB
/** * Testing Agent Auto-Launch System for Node.js * Automatically launches testing agents when tasks are completed */ import { getDbConnection } from '../db/connection.js'; import { buildAgentPrompt } from './promptTemplates.js'; import { MCP_DEBUG, getProjectDir } from '../core/config.js'; import { execSync } from 'child_process'; import * as crypto from 'crypto'; import { createTestingTask } from '../tools/testingTasks.js'; export interface TestingAgentLaunchResult { success: boolean; testing_agent_id: string; error?: string; } /** * Send escape sequences to pause an agent's tmux session */ export async function sendEscapeToAgent(agentId: string): Promise<boolean> { try { console.log(`🛑 Pausing agent ${agentId} with escape sequences`); // Get agent's tmux session from database const db = getDbConnection(); const agent = db.prepare('SELECT * FROM agents WHERE agent_id = ?').get(agentId) as any; if (!agent) { console.warn(`⚠️ Agent ${agentId} not found in database`); return false; } // Calculate session name (agent-id with last 4 chars of admin token) const adminConfig = db.prepare('SELECT config_value FROM admin_config WHERE config_key = ?').get('admin_token') as any; const adminToken = adminConfig?.config_value; if (!adminToken) { console.error('❌ Admin token not found'); return false; } const suffix = adminToken.slice(-4).toLowerCase(); const sessionName = `${agentId.replace(/[^a-zA-Z0-9_-]/g, '_')}-${suffix}`; // Send 4 escape sequences with 1 second intervals for (let i = 0; i < 4; i++) { try { execSync(`tmux send-keys -t "${sessionName}" Escape`, { timeout: 5000 }); console.log(`✅ Sent Escape ${i + 1}/4 to agent ${agentId}`); if (i < 3) { await new Promise(resolve => setTimeout(resolve, 1000)); } } catch (error) { console.error(`❌ Failed to send Escape ${i + 1}/4 to agent ${agentId}:`, error); return false; } } console.log(`✅ Successfully paused agent ${agentId}`); return true; } catch (error) { console.error(`❌ Error pausing agent ${agentId}:`, error); return false; } } /** * Generate a unique token for testing agent */ function generateTestingAgentToken(): string { return crypto.randomBytes(16).toString('hex'); } /** * Launch testing agent for a completed task */ export async function launchTestingAgentForCompletedTask( completedTaskId: string, completedByAgent: string ): Promise<TestingAgentLaunchResult> { const db = getDbConnection(); try { console.log(`🧪 Launching testing agent for completed task: ${completedTaskId}`); // 1. Send Escape sequences to pause completing agent await sendEscapeToAgent(completedByAgent); // 2. Get task details for context const task = db.prepare('SELECT * FROM tasks WHERE task_id = ?').get(completedTaskId) as any; if (!task) { console.error(`❌ Cannot find completed task ${completedTaskId} for testing`); return { success: false, testing_agent_id: '', error: 'Task not found' }; } // 3. Generate testing agent ID const testingAgentId = `test-${completedTaskId.slice(-6)}`; // 4. Clean up existing testing agent if it exists (task re-completed after fixes) const existingAgent = db.prepare('SELECT agent_id FROM agents WHERE agent_id = ?').get(testingAgentId); if (existingAgent) { console.log(`🧹 Task ${completedTaskId} re-completed - cleaning up existing testing agent ${testingAgentId}`); // Remove from database db.prepare('DELETE FROM agents WHERE agent_id = ?').run(testingAgentId); // Kill tmux session if it exists try { const adminConfig = db.prepare('SELECT config_value FROM admin_config WHERE config_key = ?').get('admin_token') as any; const adminToken = adminConfig?.config_value; const suffix = adminToken?.slice(-4).toLowerCase() || '0000'; const sessionName = `${testingAgentId.replace(/[^a-zA-Z0-9_-]/g, '_')}-${suffix}`; execSync(`tmux kill-session -t "${sessionName}"`, { timeout: 5000 }); console.log(`🧹 Killed existing tmux session for testing agent ${testingAgentId}`); } catch (error) { // Session might not exist, continue console.log(`ℹ️ No existing tmux session to kill for ${testingAgentId}`); } } // 5. Create comprehensive testing task first const adminConfig = db.prepare('SELECT config_value FROM admin_config WHERE config_key = ?').get('admin_token') as any; const adminToken = adminConfig?.config_value || ''; const testingTaskResult = await createTestingTask.execute({ completed_task_id: completedTaskId, completed_by_agent: completedByAgent, testing_agent_id: testingAgentId, admin_token: adminToken }); if (!testingTaskResult.success) { console.error(`❌ Failed to create testing task: ${testingTaskResult.error}`); return { success: false, testing_agent_id: testingAgentId, error: `Failed to create testing task: ${testingTaskResult.error}` }; } console.log(`✅ Created testing task ${testingTaskResult.testing_task_id} with:`); console.log(` - ${testingTaskResult.subtasks_found} subtasks to audit`); console.log(` - ${testingTaskResult.context_entries_found} context entries to review`); console.log(` - ${testingTaskResult.files_modified} files to check`); console.log(` - ${testingTaskResult.actions_logged} actions to analyze`); // 6. Create testing agent using the SAME method as normal agents console.log(`🤖 Creating testing agent ${testingAgentId} using normal agent creation flow...`); // Import the create_agent tool logic directly const { generateToken: authGenerateToken, registerActiveAgent } = await import('../core/auth.js'); // Generate agent data exactly like normal agents const testingToken = authGenerateToken(); const createdAt = new Date().toISOString(); const projectDir = getProjectDir(); const agentColor = '#FF0000'; // Red color for testing agents const status = 'created'; const capabilities = ['testing', 'validation', 'criticism', 'audit']; // Begin transaction (same as normal agent creation) const transaction = db.transaction(() => { // Insert agent (same structure as normal agents) const insertAgent = db.prepare(` INSERT INTO agents ( token, agent_id, capabilities, created_at, status, working_directory, color, updated_at, current_task ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `); insertAgent.run( testingToken, testingAgentId, JSON.stringify(capabilities), createdAt, status, projectDir, agentColor, createdAt, testingTaskResult.testing_task_id ); // Assign the testing task to the agent (same as normal task assignment) const updateTask = db.prepare(` UPDATE tasks SET assigned_to = ?, status = 'pending', updated_at = ? WHERE task_id = ? `); const result = updateTask.run(testingAgentId, createdAt, testingTaskResult.testing_task_id); if (result.changes === 0) { throw new Error(`Failed to assign testing task ${testingTaskResult.testing_task_id} to ${testingAgentId}`); } return testingTaskResult.testing_task_id; }); const assignedTask = transaction(); // Register agent in global state (same as normal agents) const agentData = { token: testingToken, agent_id: testingAgentId, capabilities, status: 'created' as const, current_task: assignedTask, working_directory: projectDir, color: agentColor, created_at: createdAt, updated_at: createdAt }; // Use the SAME registration method as normal agents registerActiveAgent(testingToken, agentData); console.log(`✅ Created testing agent ${testingAgentId} using normal agent flow with token ${testingToken}`); // 7. Build enriched prompt for testing agent with testing task context const prompt = buildAgentPrompt( testingAgentId, testingToken, adminToken, 'testing_agent', undefined, { testing_task_id: testingTaskResult.testing_task_id, completed_by_agent: completedByAgent, completed_task_id: completedTaskId, completed_task_title: task.title || 'Unknown', completed_task_description: task.description || 'No description', audit_summary: `Found ${testingTaskResult.subtasks_found} subtasks, ${testingTaskResult.context_entries_found} context entries, ${testingTaskResult.files_modified} files modified` } ); if (!prompt) { console.error(`❌ Failed to build prompt for testing agent ${testingAgentId}`); return { success: false, testing_agent_id: testingAgentId, error: 'Failed to build prompt' }; } // 8. Create tmux session for testing agent const suffix = adminToken.slice(-4).toLowerCase(); const sessionName = `${testingAgentId.replace(/[^a-zA-Z0-9_-]/g, '_')}-${suffix}`; try { // Create tmux session execSync(`tmux new-session -d -s "${sessionName}" -c "${projectDir}"`, { timeout: 10000 }); console.log(`✅ Created tmux session for testing agent: ${sessionName}`); // Set up environment variables const envCommands = [ `export MCP_AGENT_ID="${testingAgentId}"`, `export MCP_AGENT_TOKEN="${testingToken}"`, `export MCP_SERVER_URL="http://localhost:3001"`, `export MCP_WORKING_DIR="${projectDir}"` ]; for (const envCmd of envCommands) { execSync(`tmux send-keys -t "${sessionName}" "${envCmd}" Enter`, { timeout: 5000 }); await new Promise(resolve => setTimeout(resolve, 500)); } // Welcome message const welcomeMsg = `echo '=== Testing Agent ${testingAgentId} initialization starting ==='`; execSync(`tmux send-keys -t "${sessionName}" "${welcomeMsg}" Enter`, { timeout: 5000 }); await new Promise(resolve => setTimeout(resolve, 1000)); // Register MCP server connection const mcpAddCommand = `claude mcp add -t sse AgentMCP http://localhost:3001/sse`; execSync(`tmux send-keys -t "${sessionName}" "${mcpAddCommand}" Enter`, { timeout: 5000 }); await new Promise(resolve => setTimeout(resolve, 2000)); // Start Claude const claudeCommand = `claude --dangerously-skip-permissions`; execSync(`tmux send-keys -t "${sessionName}" "${claudeCommand}" Enter`, { timeout: 5000 }); await new Promise(resolve => setTimeout(resolve, 3000)); // Send enriched prompt after delay using two separate commands setTimeout(() => { try { // First command: type the prompt (without Enter) execSync(`tmux send-keys -t "${sessionName}" "${prompt.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, ' ')}"`, { timeout: 10000 }); console.log(`✅ Typed prompt to testing agent ${testingAgentId}`); // Second command: press Enter to send the prompt setTimeout(() => { try { execSync(`tmux send-keys -t "${sessionName}" Enter`, { timeout: 5000 }); console.log(`✅ Sent Enter to testing agent ${testingAgentId}`); } catch (error) { console.error(`❌ Failed to send Enter to testing agent ${testingAgentId}:`, error); } }, 1000); } catch (error) { console.error(`❌ Failed to send prompt to testing agent ${testingAgentId}:`, error); } }, 5000); // Log the testing agent creation db.prepare(` INSERT INTO agent_actions (agent_id, action_type, details, timestamp) VALUES (?, ?, ?, ?) `).run( 'admin', 'create_testing_agent', JSON.stringify({ testing_agent_id: testingAgentId, completed_task_id: completedTaskId, completed_by_agent: completedByAgent }), createdAt ); // Schedule enhanced testing validation after a brief delay to allow agent to start setTimeout(async () => { try { const enhancedResult = await runEnhancedTestingValidation( testingAgentId, completedByAgent, completedTaskId, {} // In real implementation, pass actual completed work data ); if (MCP_DEBUG) { console.log(`🧪 Enhanced testing result for ${testingAgentId}:`, enhancedResult); } } catch (error) { console.error(`❌ Enhanced testing validation error:`, error); } }, 15000); // 15 second delay console.log(`🧪 Testing agent ${testingAgentId} launched successfully for task ${completedTaskId} with enhanced validation`); return { success: true, testing_agent_id: testingAgentId }; } catch (error) { console.error(`❌ Failed to create tmux session for testing agent ${testingAgentId}:`, error); // Clean up database entry if session creation failed db.prepare('DELETE FROM agents WHERE agent_id = ?').run(testingAgentId); return { success: false, testing_agent_id: testingAgentId, error: `Failed to create tmux session: ${error}` }; } } catch (error) { console.error(`❌ Error launching testing agent for task ${completedTaskId}:`, error); return { success: false, testing_agent_id: '', error: String(error) }; } } /** * Auto-launch testing agents for multiple completed tasks */ export async function autoLaunchTestingAgents( completedTasks: Array<{ task_id: string; completed_by: string }> ): Promise<Array<{ task_id: string; testing_agent_launched: boolean; testing_agent_id?: string; error?: string }>> { const results = []; for (const { task_id, completed_by } of completedTasks) { try { const result = await launchTestingAgentForCompletedTask(task_id, completed_by); results.push({ task_id, testing_agent_launched: result.success, testing_agent_id: result.testing_agent_id, error: result.error }); if (MCP_DEBUG) { console.log(`🧪 Testing agent launch for task ${task_id}: ${result.success ? 'SUCCESS' : 'FAILED'}`); } } catch (error) { console.error(`❌ Failed to launch testing agent for task ${task_id}:`, error); results.push({ task_id, testing_agent_launched: false, error: String(error) }); } } return results; } /** * Send feedback message to the original agent after testing */ export async function sendTestingFeedbackToAgent( testingAgentId: string, originalAgentId: string, taskId: string, testResults: { passed: boolean; issues: string[]; recommendations: string[] } ): Promise<boolean> { try { const db = getDbConnection(); // Get testing agent token const testingAgent = db.prepare('SELECT token FROM agents WHERE agent_id = ?').get(testingAgentId); if (!testingAgent) { console.error(`❌ Testing agent ${testingAgentId} not found`); return false; } // Get original agent token const originalAgent = db.prepare('SELECT token FROM agents WHERE agent_id = ?').get(originalAgentId); if (!originalAgent) { console.error(`❌ Original agent ${originalAgentId} not found`); return false; } // Construct feedback message const statusEmoji = testResults.passed ? '✅' : '❌'; const statusText = testResults.passed ? 'PASSED' : 'FAILED'; let message = `🧪 **TESTING FEEDBACK for Task ${taskId}**\n\n`; message += `${statusEmoji} **Test Result: ${statusText}**\n\n`; if (testResults.issues.length > 0) { message += `**Issues Found:**\n`; testResults.issues.forEach((issue, i) => { message += `${i + 1}. ${issue}\n`; }); message += '\n'; } if (testResults.recommendations.length > 0) { message += `**Recommendations:**\n`; testResults.recommendations.forEach((rec, i) => { message += `${i + 1}. ${rec}\n`; }); message += '\n'; } message += `From: Testing Agent ${testingAgentId}\n`; message += `Task Status: ${testResults.passed ? 'Validated ✅' : 'Needs Revision ❌'}`; // Send message using the agent communication system const messageId = `test_feedback_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const insertMessage = db.prepare(` INSERT INTO agent_messages ( message_id, sender_id, recipient_id, message_content, message_type, priority, timestamp, delivered, read ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `); insertMessage.run( messageId, testingAgentId, originalAgentId, message, 'assistance_request', // Use assistance_request type for testing feedback testResults.passed ? 'normal' : 'high', // High priority for failures new Date().toISOString(), 0, // Not delivered yet 0 // Not read yet ); // Also send to tmux session if agent is active try { // Try to find agent's tmux session by pattern const stdout = execSync(`tmux list-sessions | grep "${originalAgentId}" | head -1 | cut -d: -f1`, { timeout: 5000 }); const sessionName = stdout.toString().trim(); if (sessionName) { const tmuxMessage = `🧪 Testing feedback received for task ${taskId}: ${statusText}`; execSync(`tmux display-message -t "${sessionName}" "${tmuxMessage}"`, { timeout: 5000 }); } } catch (tmuxError) { // Non-critical - message is still in database console.log(`⚠️ Could not send tmux notification: ${tmuxError}`); } if (MCP_DEBUG) { console.log(`✅ Testing feedback sent from ${testingAgentId} to ${originalAgentId}`); } return true; } catch (error) { console.error(`❌ Error sending testing feedback:`, error); return false; } } /** * Clean incorrect or outdated project context based on testing results */ export async function cleanIncorrectProjectContext( testingAgentId: string, taskId: string, incorrectContextKeys: string[] ): Promise<{ cleaned: number; errors: string[] }> { try { const db = getDbConnection(); const errors: string[] = []; let cleaned = 0; if (MCP_DEBUG) { console.log(`🧹 Testing agent ${testingAgentId} cleaning ${incorrectContextKeys.length} context entries`); } for (const contextKey of incorrectContextKeys) { try { // Check if context exists const existingContext = db.prepare('SELECT * FROM project_context WHERE context_key = ?').get(contextKey); if (existingContext) { // Archive the incorrect context with timestamp and reason const archiveKey = `archived_${contextKey}_${Date.now()}`; const context = existingContext as any; const archiveValue = { original_value: JSON.parse(context.value), archived_by: testingAgentId, archived_reason: `Identified as incorrect during task ${taskId} testing`, archived_at: new Date().toISOString(), original_updated_by: context.updated_by, original_updated_at: context.last_updated }; // Insert archived version db.prepare(` INSERT OR REPLACE INTO project_context (context_key, value, last_updated, updated_by, description) VALUES (?, ?, ?, ?, ?) `).run( archiveKey, JSON.stringify(archiveValue), new Date().toISOString(), testingAgentId, `Archived incorrect context from ${contextKey}` ); // Delete the incorrect context db.prepare('DELETE FROM project_context WHERE context_key = ?').run(contextKey); cleaned++; if (MCP_DEBUG) { console.log(`🧹 Cleaned context: ${contextKey} -> archived as ${archiveKey}`); } } else { errors.push(`Context key "${contextKey}" not found`); } } catch (contextError) { errors.push(`Failed to clean context "${contextKey}": ${contextError}`); } } // Log the context cleaning action const logAction = db.prepare(` INSERT INTO agent_actions ( agent_id, action_type, task_id, timestamp, details ) VALUES (?, ?, ?, ?, ?) `); const timestamp = new Date().toISOString(); logAction.run( testingAgentId, 'cleaned_project_context', taskId, timestamp, JSON.stringify({ cleaned_count: cleaned, error_count: errors.length, context_keys: incorrectContextKeys }) ); return { cleaned, errors }; } catch (error) { console.error(`❌ Error cleaning project context:`, error); return { cleaned: 0, errors: [String(error)] }; } } /** * Enhanced testing agent with feedback and context cleaning */ export async function runEnhancedTestingValidation( testingAgentId: string, originalAgentId: string, taskId: string, completedWork: any ): Promise<{ success: boolean; feedback_sent: boolean; context_cleaned: number }> { try { // Simulate testing validation (in real implementation, this would analyze the completed work) const testResults = { passed: Math.random() > 0.3, // 70% pass rate for demo issues: [ 'Implementation does not handle edge case X', 'Code comments are insufficient for complex logic' ], recommendations: [ 'Add error handling for network timeouts', 'Include unit tests for core functions', 'Update documentation with usage examples' ] }; // Send feedback to original agent const feedbackSent = await sendTestingFeedbackToAgent( testingAgentId, originalAgentId, taskId, testResults ); // Clean incorrect context if any issues found let contextCleaned = 0; if (!testResults.passed) { const incorrectContextKeys = [ `task_${taskId}_assumptions`, `outdated_implementation_notes` ]; const cleaningResult = await cleanIncorrectProjectContext( testingAgentId, taskId, incorrectContextKeys ); contextCleaned = cleaningResult.cleaned; } if (MCP_DEBUG) { console.log(`🧪 Enhanced testing validation complete: feedback_sent=${feedbackSent}, context_cleaned=${contextCleaned}`); } return { success: true, feedback_sent: feedbackSent, context_cleaned: contextCleaned }; } catch (error) { console.error(`❌ Enhanced testing validation failed:`, error); return { success: false, feedback_sent: false, context_cleaned: 0 }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rinadelph/Agent-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server