210 lines
8.5 KiB
JavaScript
210 lines
8.5 KiB
JavaScript
// Apply AgentDB runtime patch before any imports
|
|
import "./utils/agentdb-runtime-patch.js";
|
|
import "dotenv/config";
|
|
import { webResearchAgent } from "./agents/webResearchAgent.js";
|
|
import { codeReviewAgent } from "./agents/codeReviewAgent.js";
|
|
import { dataAgent } from "./agents/dataAgent.js";
|
|
import { claudeAgent } from "./agents/claudeAgent.js";
|
|
import { logger } from "./utils/logger.js";
|
|
import { startHealthServer } from "./health.js";
|
|
import { parseArgs, printHelp, validateOptions } from "./utils/cli.js";
|
|
import { getAgent, listAgents } from "./utils/agentLoader.js";
|
|
import { handleMCPCommand } from "./utils/mcpCommands.js";
|
|
import { handleReasoningBankCommand } from "./utils/reasoningbankCommands.js";
|
|
// Re-export ReasoningBank plugin for npm package users
|
|
export * as reasoningbank from "./reasoningbank/index.js";
|
|
async function runParallelMode() {
|
|
const topic = process.env.TOPIC ?? "migrate payments service";
|
|
const codeDiff = process.env.DIFF ?? "feat: add payments router and mandate checks";
|
|
const datasetHint = process.env.DATASET ?? "monthly tx volume, refunds, chargebacks";
|
|
logger.info('Starting parallel agent execution', {
|
|
topic,
|
|
diff: codeDiff.substring(0, 50),
|
|
dataset: datasetHint
|
|
});
|
|
// Stream handler for real-time output
|
|
const streamHandler = (agent) => (chunk) => {
|
|
if (process.env.ENABLE_STREAMING === 'true') {
|
|
process.stdout.write(`[${agent}] ${chunk}`);
|
|
}
|
|
};
|
|
// Each agent runs with its own context. Fan out in parallel.
|
|
const startTime = Date.now();
|
|
const [researchOut, reviewOut, dataOut] = await Promise.all([
|
|
webResearchAgent(`Give me context and risks about: ${topic}`, streamHandler('RESEARCH')),
|
|
codeReviewAgent(`Review this diff at a high level and propose tests:\n${codeDiff}`, streamHandler('CODE_REVIEW')),
|
|
dataAgent(`Analyze ${datasetHint} and report key stats.`, streamHandler('DATA'))
|
|
]);
|
|
const totalDuration = Date.now() - startTime;
|
|
logger.info('All agents completed', {
|
|
totalDuration,
|
|
agentCount: 3,
|
|
avgDuration: Math.round(totalDuration / 3)
|
|
});
|
|
// Basic reconcile step
|
|
const summary = [
|
|
"=== RESEARCH ===",
|
|
researchOut.output?.trim() ?? "",
|
|
"=== CODE REVIEW ===",
|
|
reviewOut.output?.trim() ?? "",
|
|
"=== DATA ===",
|
|
dataOut.output?.trim() ?? ""
|
|
].join("\n");
|
|
console.log(summary);
|
|
}
|
|
async function runAgentMode(agentName, task, stream, modelOverride) {
|
|
logger.info('Running agent mode', { agent: agentName, task: task.substring(0, 100), model: modelOverride || 'default' });
|
|
// Load the specified agent
|
|
const agent = getAgent(agentName);
|
|
if (!agent) {
|
|
const availableAgents = listAgents();
|
|
logger.error('Agent not found', { agent: agentName });
|
|
console.error(`\n❌ Agent '${agentName}' not found.\n`);
|
|
console.error('Available agents:');
|
|
availableAgents.slice(0, 20).forEach(a => {
|
|
console.error(` • ${a.name}: ${a.description.substring(0, 80)}...`);
|
|
});
|
|
if (availableAgents.length > 20) {
|
|
console.error(` ... and ${availableAgents.length - 20} more (use --list to see all)`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
console.log(`\n🤖 Agent: ${agent.name}`);
|
|
console.log(`📝 Description: ${agent.description}\n`);
|
|
console.log(`🎯 Task: ${task}\n`);
|
|
if (modelOverride) {
|
|
console.log(`🔧 Model: ${modelOverride}\n`);
|
|
}
|
|
console.log('⏳ Running...\n');
|
|
// Enhanced stream handler that writes to stderr for progress and stdout for content
|
|
const streamHandler = stream ? (chunk) => {
|
|
// Write progress indicators (timestamps, tool calls) to stderr
|
|
if (chunk.startsWith('\n[') || chunk.startsWith('[') || chunk.includes('🔍') || chunk.includes('✅') || chunk.includes('❌')) {
|
|
process.stderr.write(chunk);
|
|
}
|
|
else {
|
|
// Write text content to stdout
|
|
process.stdout.write(chunk);
|
|
}
|
|
// Force flush to ensure immediate visibility
|
|
if (process.stdout.uncork) {
|
|
process.stdout.uncork();
|
|
}
|
|
if (process.stderr.uncork) {
|
|
process.stderr.uncork();
|
|
}
|
|
} : undefined;
|
|
// Use Claude Agent SDK with in-SDK MCP server and optional model override
|
|
logger.info('Using Claude Agent SDK with in-SDK MCP server', { modelOverride });
|
|
const result = await claudeAgent(agent, task, streamHandler, modelOverride);
|
|
if (!stream) {
|
|
console.log('\n✅ Completed!\n');
|
|
console.log('═══════════════════════════════════════\n');
|
|
console.log(result.output);
|
|
console.log('\n═══════════════════════════════════════\n');
|
|
}
|
|
logger.info('Agent mode completed', { agent: agentName, outputLength: result.output.length, model: modelOverride || 'default' });
|
|
}
|
|
function runListMode() {
|
|
const agents = listAgents();
|
|
console.log(`\n📦 Available Agents (${agents.length} total)\n`);
|
|
// Group by category (based on directory structure)
|
|
const grouped = new Map();
|
|
agents.forEach(agent => {
|
|
const parts = agent.filePath.split('/');
|
|
const category = parts[parts.length - 2] || 'other';
|
|
if (!grouped.has(category)) {
|
|
grouped.set(category, []);
|
|
}
|
|
grouped.get(category).push(agent);
|
|
});
|
|
// Print grouped
|
|
Array.from(grouped.entries())
|
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
.forEach(([category, categoryAgents]) => {
|
|
console.log(`\n${category.toUpperCase()}:`);
|
|
categoryAgents.forEach(agent => {
|
|
console.log(` ${agent.name.padEnd(30)} ${agent.description.substring(0, 80)}`);
|
|
});
|
|
});
|
|
console.log(`\nTo use an agent:`);
|
|
console.log(` docker run --env-file .env claude-agents --agent <name> --task "Your task"\n`);
|
|
}
|
|
async function main() {
|
|
logger.setContext({ service: 'claude-agents', version: '1.0.0' });
|
|
// Parse CLI arguments
|
|
const options = parseArgs();
|
|
// Handle help
|
|
if (options.help) {
|
|
printHelp();
|
|
process.exit(0);
|
|
}
|
|
// Handle list mode
|
|
if (options.mode === 'list') {
|
|
runListMode();
|
|
process.exit(0);
|
|
}
|
|
// Handle MCP mode
|
|
if (options.mode === 'mcp') {
|
|
await handleMCPCommand(options.mcpCommand || 'start', options.mcpServer || 'all');
|
|
process.exit(0);
|
|
}
|
|
// Handle ReasoningBank mode
|
|
if (options.mode === 'reasoningbank') {
|
|
const subcommand = process.argv[3] || 'help';
|
|
await handleReasoningBankCommand(subcommand);
|
|
process.exit(0);
|
|
}
|
|
// Validate options
|
|
const validationError = validateOptions(options);
|
|
if (validationError) {
|
|
console.error(`\n❌ ${validationError}\n`);
|
|
printHelp();
|
|
process.exit(1);
|
|
}
|
|
logger.info('Starting Claude Agent SDK', { mode: options.mode });
|
|
// Propagate CLI options to environment variables for agent execution
|
|
if (options.provider) {
|
|
process.env.PROVIDER = options.provider;
|
|
logger.info('Provider set from CLI', { provider: options.provider });
|
|
}
|
|
if (options.anthropicApiKey) {
|
|
process.env.ANTHROPIC_API_KEY = options.anthropicApiKey;
|
|
}
|
|
if (options.openrouterApiKey) {
|
|
process.env.OPENROUTER_API_KEY = options.openrouterApiKey;
|
|
}
|
|
if (options.model) {
|
|
process.env.COMPLETION_MODEL = options.model;
|
|
}
|
|
// Start health check server
|
|
const healthPort = parseInt(process.env.HEALTH_PORT || '8080');
|
|
const healthServer = startHealthServer(healthPort);
|
|
try {
|
|
if (options.mode === 'agent') {
|
|
const task = options.task || process.env.TASK || '';
|
|
const agent = options.agent || process.env.AGENT || '';
|
|
const model = options.model || process.env.MODEL;
|
|
await runAgentMode(agent, task, options.stream || false, model);
|
|
}
|
|
else {
|
|
await runParallelMode();
|
|
}
|
|
logger.info('Execution completed successfully');
|
|
}
|
|
catch (err) {
|
|
logger.error('Execution failed', { error: err });
|
|
throw err;
|
|
}
|
|
finally {
|
|
// Keep health server running for container health checks
|
|
if (process.env.KEEP_ALIVE !== 'true') {
|
|
healthServer.close();
|
|
}
|
|
}
|
|
}
|
|
main().catch(err => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|
|
//# sourceMappingURL=index.js.map
|