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

157 lines • 7.63 kB
/** * ╔══════════════════════════════════════════════════════════════════════╗ * ║ OVERMIND BRIDGE — DirectiveParser (Agent-Side Protocol) ║ * ║ ║ * ║ Permet aux agents d'injecter des directives structurées dans ║ * ║ leurs réponses textuelles. Le bridge les extrait et les exécute. ║ * ║ ║ * ║ PATTERN ║ * ║ ─────── ║ * ║ Inspiré du `CONTEXT_UPDATE: step=X employe_id=Y` de bt-sms. ║ * ║ ║ * ║ L'agent écrit dans sa réponse : ║ * ║ ║ * ║ "Voici mon analyse. ║ * ║ SESSION_ID: hermes-sess-abc123 ║ * ║ CONTEXT_UPDATE: step=awaiting_description employe_id=42 ║ * ║ BRIDGE_NEXT: agent=scout prompt=\"Analyse BTC\" ║ * ║ BRIDGE_END" ║ * ║ ║ * ║ Le bridge : ║ * ║ 1. Extrait SESSION_ID, l'assigne au store ║ * ║ 2. Patche le context (state machine) ║ * ║ 3. Lance automatiquement un nouveau call vers scout ║ * ║ 4. Supprime les directives du texte retourné au client ║ * ║ ║ * ║ DIRECTIVES SUPPORTÉES ║ * ║ ────────────────────── ║ * ║ - SESSION_ID: <id> → assigne le sessionId ║ * ║ - CONTEXT_UPDATE: k=v k=v → patche le context ║ * ║ - BRIDGE_NEXT: method=X ... → déclenche un appel suivant ║ * ║ - BRIDGE_END → arrête la chaîne de next calls ║ * ║ - BRIDGE_HINT: <text> → tag/metadata (no action) ║ * ╚══════════════════════════════════════════════════════════════════════╝ */ import { createBridgeLogger } from './utils.js'; // ─── DirectiveParser ─────────────────────────────────────────────────────── export class DirectiveParser { log; constructor(opts = {}) { this.log = opts.logger ?? createBridgeLogger('directive-parser'); } /** * Parse un texte de réponse d'agent et extrait les directives. * Lignes de directive : `DIRECTIVE_NAME: value`. */ parse(responseText) { const actions = []; const errors = []; const cleanedLines = []; for (const rawLine of responseText.split('\n')) { const line = rawLine.trim(); // Détecte ligne "DIRECTIVE:" (case-insensitive) const m = line.match(/^([A-Z_]+):\s*(.*)$/); if (!m) { cleanedLines.push(rawLine); continue; } const [, name, rawValue] = m; try { const action = this.parseDirective(name, rawValue); if (action) { actions.push(action); continue; // directive consommée } cleanedLines.push(rawLine); // nom inconnu, on garde } catch (err) { errors.push(`Failed to parse ${name}: ${err.message}`); cleanedLines.push(rawLine); // on garde la ligne en cas d'erreur } } return { cleanText: cleanedLines .join('\n') .replace(/\n{3,}/g, '\n\n') .trim(), actions, errors, }; } /** * Parse une directive individuelle. Retourne null si nom inconnu. */ parseDirective(name, value) { switch (name) { case 'SESSION_ID': if (!value.trim()) throw new Error('Empty session id'); return { kind: 'session', sessionId: value.trim() }; case 'CONTEXT_UPDATE': { const patch = {}; for (const token of value.split(/\s+/)) { const eqIdx = token.indexOf('='); if (eqIdx === -1) continue; const k = token.slice(0, eqIdx).trim(); const v = token.slice(eqIdx + 1).trim(); if (k) patch[k] = decodeURIComponentSafe(v); } if (Object.keys(patch).length === 0) throw new Error('Empty CONTEXT_UPDATE'); return { kind: 'context', patch }; } case 'BRIDGE_NEXT': { // Format: method=agent.run agent=scout prompt="..." // method=agent.a2a from=scout to=analyst prompt="..." const params = parseKeyValueArgs(value); if (!params.method) throw new Error('BRIDGE_NEXT requires method=...'); return { kind: 'next', call: { jsonrpc: '2.0', id: 0, method: params.method, params }, }; } case 'BRIDGE_END': return { kind: 'end' }; case 'BRIDGE_HINT': return { kind: 'hint', text: value.trim() }; default: return null; // nom inconnu, le caller garde la ligne } } } // ─── Helpers ─────────────────────────────────────────────────────────────── /** * Parse un format "key=value key2='value with spaces' key3="value with quotes"". * Supporte quotes simples, doubles, et backslash escape. */ export function parseKeyValueArgs(input) { const result = {}; const re = /(\w+)=(?:"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|(\S+))/g; let m; while ((m = re.exec(input)) !== null) { const key = m[1]; const value = m[2] ?? m[3] ?? m[4] ?? ''; // Déchapper les séquences \\ \" const decoded = value.replace(/\\(.)/g, '$1'); result[key] = decoded; } return result; } /** * Décode une valeur URI-component si elle ressemble à du %XX, sinon la retourne brute. */ function decodeURIComponentSafe(value) { if (value.includes('%') && /%[0-9A-Fa-f]{2}/.test(value)) { try { return decodeURIComponent(value); } catch { return value; } } return value; } //# sourceMappingURL=DirectiveParser.js.map