tasq/node_modules/@claude-flow/hooks/dist/swarm/index.js

638 lines
22 KiB
JavaScript

/**
* V3 Swarm Communication Hooks
*
* Enables agent-to-agent communication, pattern broadcasting,
* consensus building, and task handoff coordination.
*
* @module @claude-flow/hooks/swarm
*/
import { EventEmitter } from 'node:events';
import { reasoningBank } from '../reasoningbank/index.js';
const DEFAULT_CONFIG = {
agentId: `agent_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
agentName: 'anonymous',
messageRetention: 3600000, // 1 hour
consensusTimeout: 30000, // 30 seconds
autoAcknowledge: true,
autoBroadcastPatterns: true,
patternBroadcastThreshold: 0.7,
};
// ============================================================================
// SwarmCommunication Class
// ============================================================================
/**
* Swarm Communication Hub
*
* Manages agent-to-agent communication within the swarm.
*/
export class SwarmCommunication extends EventEmitter {
config;
messages = new Map();
broadcasts = new Map();
consensusRequests = new Map();
handoffs = new Map();
agents = new Map();
initialized = false;
cleanupTimer;
// Metrics
metrics = {
messagesSent: 0,
messagesReceived: 0,
patternsBroadcast: 0,
consensusInitiated: 0,
consensusResolved: 0,
handoffsInitiated: 0,
handoffsCompleted: 0,
};
constructor(config = {}) {
super();
this.config = { ...DEFAULT_CONFIG, ...config };
}
/**
* Initialize swarm communication
*/
async initialize() {
if (this.initialized)
return;
// Register self in agent registry
this.registerAgent({
id: this.config.agentId,
name: this.config.agentName,
status: 'idle',
lastSeen: Date.now(),
capabilities: [],
patternsShared: 0,
handoffsReceived: 0,
handoffsCompleted: 0,
});
// Start cleanup interval (store reference to clear on shutdown)
this.cleanupTimer = setInterval(() => this.cleanup(), 60000);
// Listen for pattern storage to auto-broadcast
if (this.config.autoBroadcastPatterns) {
reasoningBank.on('pattern:stored', async (data) => {
const patterns = await reasoningBank.searchPatterns(data.id, 1);
if (patterns.length > 0 && patterns[0].pattern.quality >= this.config.patternBroadcastThreshold) {
await this.broadcastPattern(patterns[0].pattern);
}
});
}
this.initialized = true;
this.emit('initialized', { agentId: this.config.agentId });
}
/**
* Shutdown swarm communication and cleanup resources
*/
async shutdown() {
if (!this.initialized)
return;
// Clear cleanup timer to prevent memory leaks
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = undefined;
}
// Clear all maps
this.messages.clear();
this.broadcasts.clear();
this.consensusRequests.clear();
this.handoffs.clear();
this.agents.clear();
this.initialized = false;
this.emit('shutdown', { agentId: this.config.agentId });
}
// ============================================================================
// Agent-to-Agent Messaging
// ============================================================================
/**
* Send a message to another agent
*/
async sendMessage(to, content, options = {}) {
await this.ensureInitialized();
const message = {
id: `msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
from: this.config.agentId,
to,
type: options.type || 'context',
content,
metadata: options.metadata || {},
timestamp: Date.now(),
ttl: options.ttl,
priority: options.priority || 'normal',
};
this.messages.set(message.id, message);
this.metrics.messagesSent++;
this.emit('message:sent', message);
// If target agent exists, trigger delivery event
if (to === '*' || this.agents.has(to)) {
this.emit('message:delivered', message);
}
return message;
}
/**
* Get messages for this agent
*/
getMessages(options = {}) {
const now = Date.now();
let messages = Array.from(this.messages.values())
.filter(m => (m.to === this.config.agentId || m.to === '*') &&
(!m.ttl || m.timestamp + m.ttl > now));
if (options.from) {
messages = messages.filter(m => m.from === options.from);
}
if (options.type) {
messages = messages.filter(m => m.type === options.type);
}
if (options.since !== undefined) {
const sinceTime = options.since;
messages = messages.filter(m => m.timestamp > sinceTime);
}
messages.sort((a, b) => {
const priorityOrder = { critical: 0, high: 1, normal: 2, low: 3 };
const pDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
return pDiff !== 0 ? pDiff : b.timestamp - a.timestamp;
});
if (options.limit) {
messages = messages.slice(0, options.limit);
}
this.metrics.messagesReceived += messages.length;
return messages;
}
/**
* Broadcast context to all agents
*/
async broadcastContext(content, metadata = {}) {
return this.sendMessage('*', content, {
type: 'context',
priority: 'normal',
metadata,
});
}
/**
* Query other agents
*/
async queryAgents(query) {
return this.sendMessage('*', query, {
type: 'query',
priority: 'normal',
});
}
// ============================================================================
// Pattern Broadcasting
// ============================================================================
/**
* Broadcast a learned pattern to the swarm
*/
async broadcastPattern(pattern, targetAgents) {
await this.ensureInitialized();
const broadcast = {
id: `bc_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
sourceAgent: this.config.agentId,
pattern,
broadcastTime: Date.now(),
recipients: targetAgents || Array.from(this.agents.keys()),
acknowledgments: [],
};
this.broadcasts.set(broadcast.id, broadcast);
this.metrics.patternsBroadcast++;
// Update agent stats
const agentState = this.agents.get(this.config.agentId);
if (agentState) {
agentState.patternsShared++;
}
// Send as message
await this.sendMessage(targetAgents ? targetAgents.join(',') : '*', JSON.stringify({
broadcastId: broadcast.id,
strategy: pattern.strategy,
domain: pattern.domain,
quality: pattern.quality,
}), {
type: 'pattern',
priority: 'normal',
metadata: { broadcastId: broadcast.id },
});
this.emit('pattern:broadcast', broadcast);
return broadcast;
}
/**
* Acknowledge receipt of a pattern broadcast
*/
acknowledgeBroadcast(broadcastId) {
const broadcast = this.broadcasts.get(broadcastId);
if (!broadcast)
return false;
if (!broadcast.acknowledgments.includes(this.config.agentId)) {
broadcast.acknowledgments.push(this.config.agentId);
this.emit('pattern:acknowledged', { broadcastId, agentId: this.config.agentId });
}
return true;
}
/**
* Get recent pattern broadcasts
*/
getPatternBroadcasts(options = {}) {
let broadcasts = Array.from(this.broadcasts.values());
if (options.since !== undefined) {
const sinceTime = options.since;
broadcasts = broadcasts.filter(b => b.broadcastTime > sinceTime);
}
if (options.domain) {
broadcasts = broadcasts.filter(b => b.pattern.domain === options.domain);
}
if (options.minQuality !== undefined) {
const minQ = options.minQuality;
broadcasts = broadcasts.filter(b => b.pattern.quality >= minQ);
}
return broadcasts.sort((a, b) => b.broadcastTime - a.broadcastTime);
}
/**
* Import a broadcast pattern into local ReasoningBank
*/
async importBroadcastPattern(broadcastId) {
const broadcast = this.broadcasts.get(broadcastId);
if (!broadcast)
return false;
await reasoningBank.storePattern(broadcast.pattern.strategy, broadcast.pattern.domain, {
sourceAgent: broadcast.sourceAgent,
broadcastId,
imported: true,
});
this.acknowledgeBroadcast(broadcastId);
return true;
}
// ============================================================================
// Consensus Guidance
// ============================================================================
/**
* Initiate a consensus request
*/
async initiateConsensus(question, options, timeout) {
await this.ensureInitialized();
const request = {
id: `cons_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
initiator: this.config.agentId,
question,
options,
votes: new Map(),
deadline: Date.now() + (timeout || this.config.consensusTimeout),
status: 'pending',
};
this.consensusRequests.set(request.id, request);
this.metrics.consensusInitiated++;
// Broadcast the consensus request
await this.sendMessage('*', JSON.stringify({
consensusId: request.id,
question,
options,
deadline: request.deadline,
}), {
type: 'consensus',
priority: 'high',
metadata: { consensusId: request.id },
});
// Set timeout for resolution
setTimeout(() => this.resolveConsensus(request.id), timeout || this.config.consensusTimeout);
this.emit('consensus:initiated', request);
return request;
}
/**
* Vote on a consensus request
*/
voteConsensus(consensusId, vote) {
const request = this.consensusRequests.get(consensusId);
if (!request || request.status !== 'pending')
return false;
if (!request.options.includes(vote))
return false;
if (Date.now() > request.deadline)
return false;
request.votes.set(this.config.agentId, vote);
this.emit('consensus:voted', { consensusId, agentId: this.config.agentId, vote });
// Check if all known agents have voted
const agentCount = this.agents.size;
if (request.votes.size >= agentCount) {
this.resolveConsensus(consensusId);
}
return true;
}
/**
* Resolve a consensus request
*/
resolveConsensus(consensusId) {
const request = this.consensusRequests.get(consensusId);
if (!request || request.status !== 'pending')
return;
const voteCounts = new Map();
for (const vote of request.votes.values()) {
voteCounts.set(vote, (voteCounts.get(vote) || 0) + 1);
}
let winner = '';
let maxVotes = 0;
for (const [option, count] of voteCounts) {
if (count > maxVotes) {
maxVotes = count;
winner = option;
}
}
const participation = this.agents.size > 0 ? request.votes.size / this.agents.size : 0;
const confidence = request.votes.size > 0 ? maxVotes / request.votes.size : 0;
request.status = request.votes.size > 0 ? 'resolved' : 'expired';
request.result = {
winner,
confidence,
participation,
};
if (request.status === 'resolved') {
this.metrics.consensusResolved++;
}
this.emit('consensus:resolved', request);
}
/**
* Get consensus request by ID
*/
getConsensus(consensusId) {
return this.consensusRequests.get(consensusId);
}
/**
* Get pending consensus requests
*/
getPendingConsensus() {
return Array.from(this.consensusRequests.values())
.filter(r => r.status === 'pending');
}
/**
* Generate consensus guidance text
*/
generateConsensusGuidance(consensusId) {
const request = this.consensusRequests.get(consensusId);
if (!request)
return 'Consensus request not found';
const lines = [
`**Consensus: ${request.question}**`,
'',
`Status: ${request.status.toUpperCase()}`,
`Initiator: ${request.initiator}`,
'',
'**Options**:',
];
for (const option of request.options) {
const votes = Array.from(request.votes.entries())
.filter(([_, v]) => v === option)
.map(([agent]) => agent);
lines.push(`- ${option}: ${votes.length} votes`);
}
if (request.result) {
lines.push('');
lines.push(`**Result**: ${request.result.winner}`);
lines.push(`Confidence: ${(request.result.confidence * 100).toFixed(0)}%`);
lines.push(`Participation: ${(request.result.participation * 100).toFixed(0)}%`);
}
return lines.join('\n');
}
// ============================================================================
// Task Handoff
// ============================================================================
/**
* Initiate a task handoff to another agent
*/
async initiateHandoff(toAgent, taskDescription, context) {
await this.ensureInitialized();
const handoff = {
id: `ho_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
taskId: `task_${Date.now()}`,
description: taskDescription,
fromAgent: this.config.agentId,
toAgent,
context,
status: 'pending',
timestamp: Date.now(),
};
this.handoffs.set(handoff.id, handoff);
this.metrics.handoffsInitiated++;
// Send handoff message
await this.sendMessage(toAgent, JSON.stringify({
handoffId: handoff.id,
description: taskDescription,
context,
}), {
type: 'handoff',
priority: 'high',
metadata: { handoffId: handoff.id },
});
this.emit('handoff:initiated', handoff);
return handoff;
}
/**
* Accept a task handoff
*/
acceptHandoff(handoffId) {
const handoff = this.handoffs.get(handoffId);
if (!handoff || handoff.toAgent !== this.config.agentId)
return false;
if (handoff.status !== 'pending')
return false;
handoff.status = 'accepted';
// Update agent stats
const agentState = this.agents.get(this.config.agentId);
if (agentState) {
agentState.handoffsReceived++;
agentState.currentTask = handoff.description;
agentState.status = 'busy';
}
this.emit('handoff:accepted', handoff);
return true;
}
/**
* Reject a task handoff
*/
rejectHandoff(handoffId, reason) {
const handoff = this.handoffs.get(handoffId);
if (!handoff || handoff.toAgent !== this.config.agentId)
return false;
if (handoff.status !== 'pending')
return false;
handoff.status = 'rejected';
if (reason) {
handoff.context.blockers.push(reason);
}
this.emit('handoff:rejected', { handoff, reason });
return true;
}
/**
* Complete a task handoff
*/
completeHandoff(handoffId, result) {
const handoff = this.handoffs.get(handoffId);
if (!handoff || handoff.toAgent !== this.config.agentId)
return false;
if (handoff.status !== 'accepted')
return false;
handoff.status = 'completed';
handoff.completedAt = Date.now();
if (result) {
handoff.context = { ...handoff.context, ...result };
}
// Update agent stats
const agentState = this.agents.get(this.config.agentId);
if (agentState) {
agentState.handoffsCompleted++;
agentState.currentTask = undefined;
agentState.status = 'idle';
}
this.metrics.handoffsCompleted++;
this.emit('handoff:completed', handoff);
return true;
}
/**
* Get handoff by ID
*/
getHandoff(handoffId) {
return this.handoffs.get(handoffId);
}
/**
* Get pending handoffs for this agent
*/
getPendingHandoffs() {
return Array.from(this.handoffs.values())
.filter(h => h.toAgent === this.config.agentId && h.status === 'pending');
}
/**
* Generate handoff context text for Claude
*/
generateHandoffContext(handoffId) {
const handoff = this.handoffs.get(handoffId);
if (!handoff)
return 'Handoff not found';
const lines = [
`## Task Handoff from ${handoff.fromAgent}`,
'',
`**Task**: ${handoff.description}`,
`**Status**: ${handoff.status.toUpperCase()}`,
'',
];
if (handoff.context.filesModified.length > 0) {
lines.push('**Files Modified**:');
for (const file of handoff.context.filesModified) {
lines.push(`- ${file}`);
}
lines.push('');
}
if (handoff.context.patternsUsed.length > 0) {
lines.push('**Patterns Used**:');
for (const pattern of handoff.context.patternsUsed) {
lines.push(`- ${pattern}`);
}
lines.push('');
}
if (handoff.context.decisions.length > 0) {
lines.push('**Decisions Made**:');
for (const decision of handoff.context.decisions) {
lines.push(`- ${decision}`);
}
lines.push('');
}
if (handoff.context.blockers.length > 0) {
lines.push('**Blockers**:');
for (const blocker of handoff.context.blockers) {
lines.push(`- ⚠️ ${blocker}`);
}
lines.push('');
}
if (handoff.context.nextSteps.length > 0) {
lines.push('**Next Steps**:');
for (const step of handoff.context.nextSteps) {
lines.push(`- [ ] ${step}`);
}
}
return lines.join('\n');
}
// ============================================================================
// Agent Registry
// ============================================================================
/**
* Register an agent in the swarm
*/
registerAgent(agent) {
this.agents.set(agent.id, agent);
this.emit('agent:registered', agent);
}
/**
* Update agent status
*/
updateAgentStatus(agentId, status) {
const agent = this.agents.get(agentId);
if (agent) {
agent.status = status;
agent.lastSeen = Date.now();
this.emit('agent:updated', agent);
}
}
/**
* Get all registered agents
*/
getAgents() {
return Array.from(this.agents.values());
}
/**
* Get agent by ID
*/
getAgent(agentId) {
return this.agents.get(agentId);
}
// ============================================================================
// Statistics & Utilities
// ============================================================================
/**
* Get communication statistics
*/
getStats() {
return {
agentId: this.config.agentId,
agentCount: this.agents.size,
metrics: { ...this.metrics },
pendingMessages: this.getMessages({ limit: 1000 }).length,
pendingHandoffs: this.getPendingHandoffs().length,
pendingConsensus: this.getPendingConsensus().length,
};
}
/**
* Cleanup old messages and data
*/
cleanup() {
const now = Date.now();
const retention = this.config.messageRetention;
// Cleanup old messages
for (const [id, message] of this.messages) {
if (now - message.timestamp > retention) {
this.messages.delete(id);
}
}
// Cleanup old broadcasts
for (const [id, broadcast] of this.broadcasts) {
if (now - broadcast.broadcastTime > retention) {
this.broadcasts.delete(id);
}
}
// Cleanup expired consensus requests
for (const [id, request] of this.consensusRequests) {
if (request.status === 'pending' && now > request.deadline) {
this.resolveConsensus(id);
}
}
// Mark offline agents
for (const agent of this.agents.values()) {
if (now - agent.lastSeen > 300000) { // 5 minutes
agent.status = 'offline';
}
}
}
async ensureInitialized() {
if (!this.initialized) {
await this.initialize();
}
}
}
// ============================================================================
// Exports
// ============================================================================
export const swarmComm = new SwarmCommunication();
export { SwarmCommunication as default, };
//# sourceMappingURL=index.js.map