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
JavaScript
/**
* ╔══════════════════════════════════════════════════════════════════════╗
* ║ 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