Files
OpenQode/bin/smart-agent-flow.mjs
2025-12-14 00:40:14 +04:00

325 lines
11 KiB
JavaScript

/**
* Smart Agent Flow - Multi-Agent Routing System
*
* Enables Qwen to:
* 1. Read available agents (names, roles, capabilities)
* 2. Use multiple agents in a single task by delegating sub-tasks
* 3. Merge results back into the main response
*
* Components:
* - Agent Registry: Available agents with metadata
* - Orchestrator: Decides which agents to use
* - Router: Routes sub-tasks to agents
* - Merger: Combines agent outputs
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// ═══════════════════════════════════════════════════════════════
// AGENT REGISTRY
// ═══════════════════════════════════════════════════════════════
/**
* Built-in agents with their capabilities
*/
const BUILTIN_AGENTS = {
build: {
id: 'build',
name: 'Build Agent',
role: 'Full-stack development',
capabilities: ['coding', 'debugging', 'implementation', 'refactoring'],
whenToUse: 'General development tasks, implementing features, fixing bugs',
priority: 1
},
plan: {
id: 'plan',
name: 'Planning Agent',
role: 'Architecture and planning',
capabilities: ['architecture', 'design', 'task-breakdown', 'estimation'],
whenToUse: 'Complex features requiring upfront design, multi-step tasks',
priority: 2
},
test: {
id: 'test',
name: 'Testing Agent',
role: 'Quality assurance',
capabilities: ['unit-tests', 'integration-tests', 'test-strategy', 'coverage'],
whenToUse: 'Writing tests, improving coverage, test-driven development',
priority: 3
},
docs: {
id: 'docs',
name: 'Documentation Agent',
role: 'Technical writing',
capabilities: ['documentation', 'comments', 'readme', 'api-docs'],
whenToUse: 'Writing docs, improving comments, creating READMEs',
priority: 4
},
security: {
id: 'security',
name: 'Security Reviewer',
role: 'Security analysis',
capabilities: ['vulnerability-scan', 'auth-review', 'input-validation', 'secrets'],
whenToUse: 'Auth changes, handling sensitive data, security-critical code',
priority: 5
},
refactor: {
id: 'refactor',
name: 'Refactoring Agent',
role: 'Code improvement',
capabilities: ['cleanup', 'optimization', 'patterns', 'technical-debt'],
whenToUse: 'Improving code quality, reducing tech debt, applying patterns',
priority: 6
}
};
// ═══════════════════════════════════════════════════════════════
// ORCHESTRATOR CONFIGURATION
// ═══════════════════════════════════════════════════════════════
const DEFAULT_CONFIG = {
enabled: true,
maxAgentsPerRequest: 3,
maxTokensPerAgent: 2000,
mergeStrategy: 'advisory', // 'advisory' = main model merges, 'sequential' = chain outputs
autoDetect: true, // Automatically detect when to use multiple agents
};
// ═══════════════════════════════════════════════════════════════
// SMART AGENT FLOW CLASS
// ═══════════════════════════════════════════════════════════════
export class SmartAgentFlow {
constructor(config = {}) {
this.config = { ...DEFAULT_CONFIG, ...config };
this.agents = { ...BUILTIN_AGENTS };
this.activeAgents = [];
this.agentOutputs = [];
}
/**
* Load custom agents from .opencode/agent directory
*/
loadCustomAgents(projectPath) {
const agentDir = path.join(projectPath, '.opencode', 'agent');
if (!fs.existsSync(agentDir)) return;
const files = fs.readdirSync(agentDir).filter(f => f.endsWith('.md'));
for (const file of files) {
const content = fs.readFileSync(path.join(agentDir, file), 'utf8');
const name = path.basename(file, '.md');
// Parse agent metadata from markdown frontmatter or content
this.agents[name] = {
id: name,
name: this.extractTitle(content) || name,
role: 'Custom agent',
capabilities: this.extractCapabilities(content),
whenToUse: this.extractWhenToUse(content),
priority: 10,
custom: true
};
}
}
extractTitle(content) {
const match = content.match(/^#\s+(.+)$/m);
return match ? match[1].trim() : null;
}
extractCapabilities(content) {
const match = content.match(/capabilities?:?\s*(.+)/i);
if (match) {
return match[1].split(/[,;]/).map(c => c.trim().toLowerCase());
}
return ['general'];
}
extractWhenToUse(content) {
const match = content.match(/when\s*to\s*use:?\s*(.+)/i);
return match ? match[1].trim() : 'Custom agent for specialized tasks';
}
/**
* Get all available agents
*/
getAgents() {
return Object.values(this.agents);
}
/**
* Get agent by ID
*/
getAgent(id) {
return this.agents[id] || null;
}
/**
* Analyze request to determine if multi-agent mode is beneficial
*/
analyzeRequest(request) {
if (!this.config.autoDetect || !this.config.enabled) {
return { useMultiAgent: false, reason: 'Multi-agent mode disabled' };
}
const requestLower = request.toLowerCase();
// Patterns that suggest multi-agent mode
const patterns = {
multiDiscipline: /\b(frontend|backend|database|api|ui|server|client)\b.*\b(and|with|plus)\b.*\b(frontend|backend|database|api|ui|server|client)\b/i,
highRisk: /\b(auth|authentication|authorization|permission|password|secret|token|security)\b/i,
largeRefactor: /\b(refactor|rewrite|restructure|reorganize)\b.*\b(entire|whole|all|complete)\b/i,
needsReview: /\b(review|check|verify|validate|audit)\b.*\b(security|code|implementation)\b/i,
needsPlanning: /\b(plan|design|architect|strategy)\b.*\b(before|first|then)\b/i,
needsTests: /\b(test|coverage|tdd|unit test|integration)\b/i,
needsDocs: /\b(document|readme|api docs|comments)\b/i
};
const detectedPatterns = [];
const suggestedAgents = new Set(['build']); // Always include build
if (patterns.multiDiscipline.test(requestLower)) {
detectedPatterns.push('multi-discipline');
suggestedAgents.add('plan');
}
if (patterns.highRisk.test(requestLower)) {
detectedPatterns.push('security-sensitive');
suggestedAgents.add('security');
}
if (patterns.largeRefactor.test(requestLower)) {
detectedPatterns.push('large-refactor');
suggestedAgents.add('refactor');
suggestedAgents.add('plan');
}
if (patterns.needsReview.test(requestLower)) {
detectedPatterns.push('needs-review');
suggestedAgents.add('security');
}
if (patterns.needsPlanning.test(requestLower)) {
detectedPatterns.push('needs-planning');
suggestedAgents.add('plan');
}
if (patterns.needsTests.test(requestLower)) {
detectedPatterns.push('needs-tests');
suggestedAgents.add('test');
}
if (patterns.needsDocs.test(requestLower)) {
detectedPatterns.push('needs-docs');
suggestedAgents.add('docs');
}
// Use multi-agent if more than one pattern detected
const useMultiAgent = suggestedAgents.size > 1 && detectedPatterns.length > 0;
return {
useMultiAgent,
reason: useMultiAgent
? `Detected: ${detectedPatterns.join(', ')}`
: 'Single-agent sufficient for this request',
suggestedAgents: Array.from(suggestedAgents).slice(0, this.config.maxAgentsPerRequest),
patterns: detectedPatterns
};
}
/**
* Start multi-agent flow for a request
*/
startFlow(agentIds) {
this.activeAgents = agentIds.map(id => this.agents[id]).filter(Boolean);
this.agentOutputs = [];
return this.activeAgents;
}
/**
* Record agent output
*/
recordOutput(agentId, output) {
this.agentOutputs.push({
agentId,
agent: this.agents[agentId],
output,
timestamp: Date.now()
});
}
/**
* Get summary for UI display
*/
getFlowStatus() {
return {
active: this.activeAgents.length > 0,
agents: this.activeAgents.map(a => ({
id: a.id,
name: a.name,
role: a.role
})),
outputs: this.agentOutputs.length,
enabled: this.config.enabled
};
}
/**
* Build context for model about available agents
*/
buildAgentContext() {
const agents = this.getAgents();
let context = `\n## Available Agents\n\nYou have access to the following specialized agents:\n\n`;
for (const agent of agents) {
context += `### ${agent.name} (${agent.id})\n`;
context += `- **Role**: ${agent.role}\n`;
context += `- **Capabilities**: ${agent.capabilities.join(', ')}\n`;
context += `- **When to use**: ${agent.whenToUse}\n\n`;
}
context += `## Multi-Agent Guidelines\n\n`;
context += `Use multiple agents when:\n`;
context += `- The request spans multiple disciplines (UI + backend + DB + deployment)\n`;
context += `- Risk is high (auth, permissions, data loss)\n`;
context += `- Large refactor needed and you want a review pass\n\n`;
context += `Do NOT use multiple agents when:\n`;
context += `- Small changes or trivial questions\n`;
context += `- User asked for speed or minimal output\n`;
context += `- No clear benefit from additional perspectives\n`;
return context;
}
/**
* Toggle multi-agent mode
*/
toggle(enabled = null) {
if (enabled === null) {
this.config.enabled = !this.config.enabled;
} else {
this.config.enabled = enabled;
}
return this.config.enabled;
}
/**
* Reset flow state
*/
reset() {
this.activeAgents = [];
this.agentOutputs = [];
}
}
// Singleton instance
let smartAgentFlowInstance = null;
export function getSmartAgentFlow(config) {
if (!smartAgentFlowInstance) {
smartAgentFlowInstance = new SmartAgentFlow(config);
}
return smartAgentFlowInstance;
}
export default SmartAgentFlow;