443 lines
12 KiB
JavaScript
443 lines
12 KiB
JavaScript
/**
|
|
* V3 Daemon Manager
|
|
*
|
|
* Manages background daemon processes for:
|
|
* - Metrics collection
|
|
* - Swarm monitoring
|
|
* - Pattern learning consolidation
|
|
* - Statusline updates
|
|
*/
|
|
/**
|
|
* Default daemon manager configuration
|
|
*/
|
|
const DEFAULT_CONFIG = {
|
|
pidDirectory: '.claude-flow/pids',
|
|
logDirectory: '.claude-flow/logs',
|
|
daemons: [],
|
|
autoRestart: true,
|
|
maxRestartAttempts: 3,
|
|
};
|
|
/**
|
|
* Daemon Manager - controls background daemon processes
|
|
*/
|
|
export class DaemonManager {
|
|
config;
|
|
daemons = new Map();
|
|
restartCounts = new Map();
|
|
constructor(config) {
|
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
}
|
|
/**
|
|
* Register a daemon
|
|
*/
|
|
register(config, task) {
|
|
if (this.daemons.has(config.name)) {
|
|
throw new Error(`Daemon '${config.name}' is already registered`);
|
|
}
|
|
const state = {
|
|
name: config.name,
|
|
status: 'stopped',
|
|
executionCount: 0,
|
|
failureCount: 0,
|
|
};
|
|
this.daemons.set(config.name, { config, state, task });
|
|
}
|
|
/**
|
|
* Start a daemon
|
|
*/
|
|
async start(name) {
|
|
const daemon = this.daemons.get(name);
|
|
if (!daemon) {
|
|
throw new Error(`Daemon '${name}' not found`);
|
|
}
|
|
if (daemon.state.status === 'running') {
|
|
return; // Already running
|
|
}
|
|
if (!daemon.config.enabled) {
|
|
throw new Error(`Daemon '${name}' is disabled`);
|
|
}
|
|
daemon.state.status = 'starting';
|
|
daemon.state.startedAt = new Date();
|
|
try {
|
|
// Start interval timer
|
|
daemon.timer = setInterval(async () => {
|
|
await this.executeDaemonTask(name);
|
|
}, daemon.config.interval);
|
|
daemon.state.status = 'running';
|
|
daemon.state.pid = process.pid; // Use current process for in-process daemons
|
|
// Run initial execution
|
|
await this.executeDaemonTask(name);
|
|
}
|
|
catch (error) {
|
|
daemon.state.status = 'error';
|
|
daemon.state.error = error instanceof Error ? error.message : String(error);
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Stop a daemon
|
|
*/
|
|
async stop(name) {
|
|
const daemon = this.daemons.get(name);
|
|
if (!daemon) {
|
|
throw new Error(`Daemon '${name}' not found`);
|
|
}
|
|
if (daemon.state.status === 'stopped') {
|
|
return; // Already stopped
|
|
}
|
|
daemon.state.status = 'stopping';
|
|
if (daemon.timer) {
|
|
clearInterval(daemon.timer);
|
|
daemon.timer = undefined;
|
|
}
|
|
daemon.state.status = 'stopped';
|
|
daemon.state.pid = undefined;
|
|
}
|
|
/**
|
|
* Restart a daemon
|
|
*/
|
|
async restart(name) {
|
|
await this.stop(name);
|
|
await this.start(name);
|
|
}
|
|
/**
|
|
* Start all registered daemons
|
|
*/
|
|
async startAll() {
|
|
const promises = [];
|
|
for (const [name, daemon] of this.daemons) {
|
|
if (daemon.config.enabled) {
|
|
promises.push(this.start(name));
|
|
}
|
|
}
|
|
await Promise.all(promises);
|
|
}
|
|
/**
|
|
* Stop all daemons
|
|
*/
|
|
async stopAll() {
|
|
const promises = [];
|
|
for (const name of this.daemons.keys()) {
|
|
promises.push(this.stop(name));
|
|
}
|
|
await Promise.all(promises);
|
|
}
|
|
/**
|
|
* Get daemon state
|
|
*/
|
|
getState(name) {
|
|
return this.daemons.get(name)?.state;
|
|
}
|
|
/**
|
|
* Get all daemon states
|
|
*/
|
|
getAllStates() {
|
|
return Array.from(this.daemons.values()).map((d) => d.state);
|
|
}
|
|
/**
|
|
* Check if daemon is running
|
|
*/
|
|
isRunning(name) {
|
|
return this.daemons.get(name)?.state.status === 'running';
|
|
}
|
|
/**
|
|
* Update daemon interval
|
|
*/
|
|
updateInterval(name, interval) {
|
|
const daemon = this.daemons.get(name);
|
|
if (!daemon) {
|
|
throw new Error(`Daemon '${name}' not found`);
|
|
}
|
|
daemon.config.interval = interval;
|
|
// Restart if running to apply new interval
|
|
if (daemon.state.status === 'running') {
|
|
this.restart(name).catch(() => { });
|
|
}
|
|
}
|
|
/**
|
|
* Enable a daemon
|
|
*/
|
|
enable(name) {
|
|
const daemon = this.daemons.get(name);
|
|
if (daemon) {
|
|
daemon.config.enabled = true;
|
|
}
|
|
}
|
|
/**
|
|
* Disable a daemon
|
|
*/
|
|
disable(name) {
|
|
const daemon = this.daemons.get(name);
|
|
if (daemon) {
|
|
daemon.config.enabled = false;
|
|
this.stop(name).catch(() => { });
|
|
}
|
|
}
|
|
/**
|
|
* Get daemon count
|
|
*/
|
|
get count() {
|
|
return this.daemons.size;
|
|
}
|
|
/**
|
|
* Get running daemon count
|
|
*/
|
|
get runningCount() {
|
|
return Array.from(this.daemons.values()).filter((d) => d.state.status === 'running').length;
|
|
}
|
|
/**
|
|
* Execute a daemon task
|
|
*/
|
|
async executeDaemonTask(name) {
|
|
const daemon = this.daemons.get(name);
|
|
if (!daemon || !daemon.task) {
|
|
return;
|
|
}
|
|
try {
|
|
await daemon.task();
|
|
daemon.state.executionCount++;
|
|
daemon.state.lastUpdateAt = new Date();
|
|
daemon.state.error = undefined;
|
|
// Reset restart count on successful execution
|
|
this.restartCounts.set(name, 0);
|
|
}
|
|
catch (error) {
|
|
daemon.state.failureCount++;
|
|
daemon.state.error = error instanceof Error ? error.message : String(error);
|
|
// Handle auto-restart
|
|
if (this.config.autoRestart) {
|
|
const restartCount = (this.restartCounts.get(name) ?? 0) + 1;
|
|
this.restartCounts.set(name, restartCount);
|
|
if (restartCount <= this.config.maxRestartAttempts) {
|
|
// Schedule restart
|
|
setTimeout(() => {
|
|
this.restart(name).catch(() => { });
|
|
}, 1000 * restartCount); // Exponential backoff
|
|
}
|
|
else {
|
|
daemon.state.status = 'error';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Metrics Daemon - collects and syncs metrics
|
|
*/
|
|
export class MetricsDaemon {
|
|
manager;
|
|
metricsStore = new Map();
|
|
constructor(manager) {
|
|
this.manager = manager ?? new DaemonManager();
|
|
// Register metrics daemon
|
|
this.manager.register({
|
|
name: 'metrics-sync',
|
|
interval: 30000, // 30 seconds
|
|
enabled: true,
|
|
}, () => this.syncMetrics());
|
|
}
|
|
/**
|
|
* Start metrics collection
|
|
*/
|
|
async start() {
|
|
await this.manager.start('metrics-sync');
|
|
}
|
|
/**
|
|
* Stop metrics collection
|
|
*/
|
|
async stop() {
|
|
await this.manager.stop('metrics-sync');
|
|
}
|
|
/**
|
|
* Sync metrics
|
|
*/
|
|
async syncMetrics() {
|
|
// Collect various metrics
|
|
this.metricsStore.set('timestamp', new Date().toISOString());
|
|
this.metricsStore.set('memory', process.memoryUsage());
|
|
// Additional metrics would be collected here
|
|
}
|
|
/**
|
|
* Get current metrics
|
|
*/
|
|
getMetrics() {
|
|
return Object.fromEntries(this.metricsStore);
|
|
}
|
|
}
|
|
/**
|
|
* Swarm Monitor Daemon - monitors swarm activity
|
|
*/
|
|
export class SwarmMonitorDaemon {
|
|
manager;
|
|
swarmData = {
|
|
activeAgents: 0,
|
|
maxAgents: 15,
|
|
coordinationActive: false,
|
|
lastCheck: null,
|
|
};
|
|
constructor(manager) {
|
|
this.manager = manager ?? new DaemonManager();
|
|
// Register swarm monitor daemon
|
|
this.manager.register({
|
|
name: 'swarm-monitor',
|
|
interval: 3000, // 3 seconds
|
|
enabled: true,
|
|
}, () => this.checkSwarm());
|
|
}
|
|
/**
|
|
* Start swarm monitoring
|
|
*/
|
|
async start() {
|
|
await this.manager.start('swarm-monitor');
|
|
}
|
|
/**
|
|
* Stop swarm monitoring
|
|
*/
|
|
async stop() {
|
|
await this.manager.stop('swarm-monitor');
|
|
}
|
|
/**
|
|
* Check swarm status
|
|
*/
|
|
async checkSwarm() {
|
|
// In a real implementation, this would check running processes
|
|
// and coordination state
|
|
this.swarmData.lastCheck = new Date();
|
|
}
|
|
/**
|
|
* Get swarm data
|
|
*/
|
|
getSwarmData() {
|
|
return { ...this.swarmData };
|
|
}
|
|
/**
|
|
* Update active agent count
|
|
*/
|
|
updateAgentCount(count) {
|
|
this.swarmData.activeAgents = count;
|
|
}
|
|
/**
|
|
* Set coordination state
|
|
*/
|
|
setCoordinationActive(active) {
|
|
this.swarmData.coordinationActive = active;
|
|
}
|
|
}
|
|
/**
|
|
* Hooks Learning Daemon - consolidates learned patterns using ReasoningBank
|
|
*/
|
|
export class HooksLearningDaemon {
|
|
manager;
|
|
patternsLearned = 0;
|
|
routingAccuracy = 0;
|
|
reasoningBank = null;
|
|
lastConsolidation = null;
|
|
consolidationStats = {
|
|
totalRuns: 0,
|
|
patternsPromoted: 0,
|
|
patternsPruned: 0,
|
|
duplicatesRemoved: 0,
|
|
};
|
|
constructor(manager) {
|
|
this.manager = manager ?? new DaemonManager();
|
|
// Register hooks learning daemon
|
|
this.manager.register({
|
|
name: 'hooks-learning',
|
|
interval: 60000, // 60 seconds
|
|
enabled: true,
|
|
}, () => this.consolidate());
|
|
}
|
|
/**
|
|
* Start learning consolidation
|
|
*/
|
|
async start() {
|
|
// Lazy load ReasoningBank to avoid circular dependencies
|
|
try {
|
|
const { reasoningBank } = await import('../reasoningbank/index.js');
|
|
this.reasoningBank = reasoningBank;
|
|
await this.reasoningBank.initialize();
|
|
}
|
|
catch (error) {
|
|
console.warn('[HooksLearningDaemon] ReasoningBank not available:', error);
|
|
}
|
|
await this.manager.start('hooks-learning');
|
|
}
|
|
/**
|
|
* Stop learning consolidation
|
|
*/
|
|
async stop() {
|
|
await this.manager.stop('hooks-learning');
|
|
}
|
|
/**
|
|
* Consolidate learned patterns using ReasoningBank
|
|
*/
|
|
async consolidate() {
|
|
if (!this.reasoningBank) {
|
|
return;
|
|
}
|
|
try {
|
|
const result = await this.reasoningBank.consolidate();
|
|
// Update stats
|
|
this.consolidationStats.totalRuns++;
|
|
this.consolidationStats.patternsPromoted += result.patternsPromoted;
|
|
this.consolidationStats.patternsPruned += result.patternsPruned;
|
|
this.consolidationStats.duplicatesRemoved += result.duplicatesRemoved;
|
|
this.lastConsolidation = new Date();
|
|
// Update pattern count from ReasoningBank stats
|
|
const stats = this.reasoningBank.getStats();
|
|
this.patternsLearned = stats.shortTermCount + stats.longTermCount;
|
|
// Emit consolidation event
|
|
if (result.patternsPromoted > 0 || result.patternsPruned > 0) {
|
|
console.log(`[HooksLearningDaemon] Consolidated: ${result.patternsPromoted} promoted, ` +
|
|
`${result.patternsPruned} pruned, ${result.duplicatesRemoved} deduped`);
|
|
}
|
|
}
|
|
catch (error) {
|
|
console.error('[HooksLearningDaemon] Consolidation failed:', error);
|
|
}
|
|
}
|
|
/**
|
|
* Get learning stats
|
|
*/
|
|
getStats() {
|
|
return {
|
|
patternsLearned: this.patternsLearned,
|
|
routingAccuracy: this.routingAccuracy,
|
|
consolidationStats: { ...this.consolidationStats },
|
|
lastConsolidation: this.lastConsolidation,
|
|
};
|
|
}
|
|
/**
|
|
* Update pattern count
|
|
*/
|
|
updatePatternCount(count) {
|
|
this.patternsLearned = count;
|
|
}
|
|
/**
|
|
* Update routing accuracy
|
|
*/
|
|
updateRoutingAccuracy(accuracy) {
|
|
this.routingAccuracy = accuracy;
|
|
}
|
|
/**
|
|
* Get ReasoningBank stats (if available)
|
|
*/
|
|
getReasoningBankStats() {
|
|
if (!this.reasoningBank) {
|
|
return null;
|
|
}
|
|
return this.reasoningBank.getStats();
|
|
}
|
|
/**
|
|
* Force immediate consolidation
|
|
*/
|
|
async forceConsolidate() {
|
|
await this.consolidate();
|
|
}
|
|
}
|
|
/**
|
|
* Default daemon manager instance
|
|
*/
|
|
export const defaultDaemonManager = new DaemonManager();
|
|
export { DaemonManager as default, };
|
|
//# sourceMappingURL=index.js.map
|