UNPKG

overmind-mcp

Version:

Orchestrateur universel agents IA multi-modeles via MCP. Inclut le protocole 'Custom-Nickname' pour identifier vos agents avec des surnoms originaux (The Chaos Prophet, Shadow Sniper, etc.), l'isolation mémoire (Private Memory Context) et le support pour

207 lines • 7.88 kB
/** * ╔══════════════════════════════════════════════════════════════════════╗ * ║ OVERMIND BRIDGE — AgentRegistry (In-Memory State Tracker) ║ * ║ ║ * ║ Tracke l'état live de chaque agent : busy / idle / offline. ║ * ║ Sérialise les appels concurrents vers un même agent via mutex. ║ * ║ ║ * ║ ARCHITECTURE ║ * ║ ───────────── ║ * ║ OverBridgeServer → AgentRegistry → (mutex per agent) ║ * ║ → OverBridgeService.runAgent() ║ * ╚══════════════════════════════════════════════════════════════════════╝ */ import { Mutex } from 'async-mutex'; import { createBridgeLogger } from './utils.js'; // ─── AgentRegistry ──────────────────────────────────────────────────────── export class AgentRegistry { states = new Map(); mutexes = new Map(); log; constructor(logger) { this.log = logger ?? createBridgeLogger('agent-registry'); } // ─── Mutex Helpers ────────────────────────────────────────────────────── /** * Récupère (ou crée) le mutex associé à un agent. * Garantit qu'un seul appel runAgent tourne à la fois par agent. */ getMutex(agentName) { let mutex = this.mutexes.get(agentName); if (!mutex) { mutex = new Mutex(); this.mutexes.set(agentName, mutex); } return mutex; } /** * Exécute une fonction sous le mutex de l'agent. * Bloque si un autre run est en cours sur le même agent. */ async withLock(agentName, fn) { return this.getMutex(agentName).runExclusive(fn); } /** * Vérifie si un run est actuellement en cours sur cet agent. */ isBusy(agentName) { return this.getMutex(agentName).isLocked(); } // ─── State Management ─────────────────────────────────────────────────── /** * Initialise ou met à jour l'état d'un agent. * Appelé au premier runAgent ou explicitement via register(). */ register(agentName, runner) { const existing = this.states.get(agentName); if (existing) { existing.runner = runner; // Update runner if changed return existing; } const state = { name: agentName, runner, status: 'online', lastActivityAt: Date.now(), totalRuns: 0, totalErrors: 0, a2aReceived: 0, a2aSent: 0, }; this.states.set(agentName, state); this.log.info(`📝 Agent registered: ${agentName} (${runner})`); return state; } /** * Marque un agent comme busy (run en cours). */ markBusy(agentName, sessionId, pid) { const state = this.states.get(agentName); if (!state) return; state.status = 'busy'; state.currentSessionId = sessionId; state.pid = pid; state.lastActivityAt = Date.now(); } /** * Marque un agent comme idle (run terminé, agent dispo). */ markIdle(agentName, success) { const state = this.states.get(agentName); if (!state) return; state.status = 'online'; state.currentSessionId = undefined; state.lastActivityAt = Date.now(); state.totalRuns++; if (!success) state.totalErrors++; } /** * Marque un agent comme offline (kill, crash, ou jamais vu). */ markOffline(agentName) { const state = this.states.get(agentName); if (!state) return; state.status = 'offline'; state.currentSessionId = undefined; state.pid = undefined; } /** * Marque un agent comme online (vu récemment, pas en cours). */ markOnline(agentName) { const state = this.states.get(agentName); if (!state) return; state.status = 'online'; state.lastActivityAt = Date.now(); } /** * Incrémente le compteur A2A received (cet agent a été appelé par un autre). */ incrementA2aReceived(agentName) { const state = this.states.get(agentName); if (state) state.a2aReceived++; } /** * Incrémente le compteur A2A sent (cet agent a appelé un autre). */ incrementA2aSent(agentName) { const state = this.states.get(agentName); if (state) state.a2aSent++; } // ─── Queries ──────────────────────────────────────────────────────────── /** * Récupère l'état d'un agent. Retourne undefined si jamais vu. */ get(agentName) { const state = this.states.get(agentName); return state ? { ...state } : undefined; } /** * Liste tous les agents connus, optionnellement filtrés. */ list(filter) { const all = Array.from(this.states.values()).map((s) => ({ ...s })); if (!filter) return all; return all.filter((s) => { if (filter.status && s.status !== filter.status) return false; if (filter.runner && s.runner !== filter.runner) return false; return true; }); } /** * Liste les agents actuellement busy (utile pour /status global). */ listBusy() { return this.list({ status: 'busy' }); } /** * Liste les agents online et idle (disponibles pour run). */ listAvailable() { return this.list().filter((s) => s.status === 'online' || s.status === 'idle'); } /** * Statistiques globales du registry. */ stats() { const all = Array.from(this.states.values()); return { total: all.length, online: all.filter((s) => s.status === 'online').length, busy: all.filter((s) => s.status === 'busy').length, offline: all.filter((s) => s.status === 'offline').length, totalRuns: all.reduce((acc, s) => acc + s.totalRuns, 0), totalErrors: all.reduce((acc, s) => acc + s.totalErrors, 0), }; } /** * Purge les agents offline depuis plus de `maxAgeMs`. * Évite que la map grossisse indéfiniment. */ prune(maxAgeMs = 24 * 60 * 60 * 1000) { const cutoff = Date.now() - maxAgeMs; let pruned = 0; for (const [name, state] of this.states) { if (state.status === 'offline' && state.lastActivityAt < cutoff) { this.states.delete(name); this.mutexes.delete(name); pruned++; } } if (pruned > 0) { this.log.info(`🧹 Pruned ${pruned} stale offline agents`); } return pruned; } } //# sourceMappingURL=AgentRegistry.js.map