{"_id":"@aidalinfo/hermes-bridge","_rev":"5-c258b137c3badd1616e6431d4bcf6d3a","name":"@aidalinfo/hermes-bridge","dist-tags":{"latest":"0.1.4"},"versions":{"0.1.0":{"name":"@aidalinfo/hermes-bridge","version":"0.1.0","license":"MIT","_id":"@aidalinfo/hermes-bridge@0.1.0","maintainers":[{"name":"aidalinfo","email":"dev@pulsemyit.fr"}],"bin":{"hermes-bridge":"dist/cli/bin.js"},"dist":{"shasum":"688145c91f706412ff917e4c6521939c478c71f1","tarball":"https://registry.npmjs.org/@aidalinfo/hermes-bridge/-/hermes-bridge-0.1.0.tgz","fileCount":15,"integrity":"sha512-KFQuEkgvCROQwzvhA4sglc9yWHaAcnHqMFa1Rl0sfgrb3C3PAey+bAfonfq4kTvs/bMKcO1pjwB9lH17FMHazw==","signatures":[{"sig":"MEUCICWD8muMQ1CgGutSEwTFy4r3dCI+DcDZqt3zK8vGpPorAiEA2Gmm6HK7eK3W1IjUKSoyaJsjceLEq2B7U+Ah6zjiSYE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":25516},"type":"module","gitHead":"e98368b44593d757f88c5d77b50d5f2e798003fe","scripts":{"dev":"tsx src/server/index.ts","lint":"tsc --noEmit","test":"vitest run","build":"tsc"},"_npmUser":{"name":"aidalinfo","email":"dev@pulsemyit.fr"},"_npmVersion":"10.9.8","description":"Relais MCP pour la communication synchrone entre agents Hermes.","directories":{},"_nodeVersion":"22.23.0","dependencies":{"ws":"^8.18.0","zod":"^4.4.3","js-yaml":"^4.1.0","@modelcontextprotocol/sdk":"^1.29.0"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","vitest":"^2.1.0","@types/ws":"^8.5.13","typescript":"^5.7.0","@types/node":"^22.10.0","@types/js-yaml":"^4.0.9"},"_npmOperationalInternal":{"tmp":"tmp/hermes-bridge_0.1.0_1782852033122_0.1061477164903546","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@aidalinfo/hermes-bridge","version":"0.1.1","license":"MIT","_id":"@aidalinfo/hermes-bridge@0.1.1","maintainers":[{"name":"aidalinfo","email":"dev@pulsemyit.fr"}],"bin":{"hermes-bridge":"dist/cli/bin.js"},"dist":{"shasum":"3c130ac8be8e42d61cd31ec6d6cbaf101b318358","tarball":"https://registry.npmjs.org/@aidalinfo/hermes-bridge/-/hermes-bridge-0.1.1.tgz","fileCount":18,"integrity":"sha512-TaDmUzVV5vA9h5cj4pH+at0ybFsBJj3vx+NGeUvvfvxHqGWcPAAsmCoJK3LqXur5Ax1mw9kFThBvdRwGBBN5Zg==","signatures":[{"sig":"MEQCIAdyhmxGHGpFHaRAGonda0uGk0ulD74XVuWUA4mPU8AmAiBQaTgiagASFuJivyUkj7ISEdp/pc308puIqaqzjDjm1Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":33695},"type":"module","gitHead":"ddba964750ad8c707f663dcb693c50f8089a528e","scripts":{"dev":"tsx src/server/index.ts","lint":"tsc --noEmit","test":"vitest run","build":"tsc"},"_npmUser":{"name":"aidalinfo","email":"dev@pulsemyit.fr"},"_npmVersion":"10.9.8","description":"Relais MCP pour la communication synchrone entre agents Hermes.","directories":{},"_nodeVersion":"22.23.1","dependencies":{"ws":"^8.18.0","zod":"^4.4.3","js-yaml":"^4.1.0","langfuse":"^3.38.20","@modelcontextprotocol/sdk":"^1.29.0"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","vitest":"^2.1.0","@types/ws":"^8.5.13","typescript":"^5.7.0","@types/node":"^22.10.0","@types/js-yaml":"^4.0.9"},"_npmOperationalInternal":{"tmp":"tmp/hermes-bridge_0.1.1_1782894476995_0.45570711510863493","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@aidalinfo/hermes-bridge","version":"0.1.2","license":"MIT","_id":"@aidalinfo/hermes-bridge@0.1.2","maintainers":[{"name":"aidalinfo","email":"dev@pulsemyit.fr"}],"bin":{"hermes-bridge":"dist/cli/bin.js"},"dist":{"shasum":"05d83cd81604db0575140f220546ce6cc1599240","tarball":"https://registry.npmjs.org/@aidalinfo/hermes-bridge/-/hermes-bridge-0.1.2.tgz","fileCount":18,"integrity":"sha512-JW1PDq1T6wguNRkxq325taATvLvEYU2/+TSWTgdJdpDDOmSAUK8KUeK2xfUeO4rQZinu7e1sdbMIE7wPgAosGA==","signatures":[{"sig":"MEQCIHq86WZZuHLZBjxFdpZgB1d86OgwekWxLueoBbEVv7sKAiBWB4cV0260Z1odaf4phpnDP+boLxrli1TEoXub9x4OsA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":40561},"type":"module","gitHead":"fa42fb553baa7b72829e6f354176134834261ad8","scripts":{"dev":"tsx src/server/index.ts","lint":"tsc --noEmit","test":"vitest run","build":"tsc"},"_npmUser":{"name":"aidalinfo","email":"dev@pulsemyit.fr"},"_npmVersion":"10.9.8","description":"Relais MCP pour la communication synchrone entre agents Hermes.","directories":{},"_nodeVersion":"22.23.0","dependencies":{"ws":"^8.18.0","zod":"^4.4.3","js-yaml":"^4.1.0","langfuse":"^3.38.20","@modelcontextprotocol/sdk":"^1.29.0"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","vitest":"^2.1.0","@types/ws":"^8.5.13","typescript":"^5.7.0","@types/node":"^22.10.0","@types/js-yaml":"^4.0.9"},"_npmOperationalInternal":{"tmp":"tmp/hermes-bridge_0.1.2_1782898136932_0.5161058759548354","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@aidalinfo/hermes-bridge","version":"0.1.3","license":"MIT","_id":"@aidalinfo/hermes-bridge@0.1.3","maintainers":[{"name":"aidalinfo","email":"dev@pulsemyit.fr"}],"bin":{"hermes-bridge":"dist/cli/bin.js"},"dist":{"shasum":"4bd85d4f3378266a3d67ed11e5e276d213d1b308","tarball":"https://registry.npmjs.org/@aidalinfo/hermes-bridge/-/hermes-bridge-0.1.3.tgz","fileCount":18,"integrity":"sha512-ysrNWcN0d+2Auq11GX1UxtaX8YM0RXk0z0PNzq5quaczokGni6Hcdx7DXcVQ1Bf81ZM+8icrS1v2WFHwwSPW/Q==","signatures":[{"sig":"MEYCIQDraZXphV7OwoXMcCOTw9PThFiyO2GC/UK6j6VYOCMvkAIhANk89kmPb42GCC9l7r+ZpkK/a+NiTY5VMPh76F5BG1NQ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":59484},"type":"module","gitHead":"e4ca09a9e0f10689634a8fdb8a5aa32d62352bf5","scripts":{"dev":"tsx src/server/index.ts","lint":"tsc --noEmit","test":"vitest run","build":"tsc"},"_npmUser":{"name":"aidalinfo","email":"dev@pulsemyit.fr"},"_npmVersion":"10.9.8","description":"Relais MCP pour la communication synchrone entre agents Hermes.","directories":{},"_nodeVersion":"22.23.0","dependencies":{"ws":"^8.18.0","zod":"^4.4.3","js-yaml":"^4.1.0","langfuse":"^3.38.20","@modelcontextprotocol/sdk":"^1.29.0"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.19.0","vitest":"^2.1.0","@types/ws":"^8.5.13","typescript":"^5.7.0","@types/node":"^22.10.0","@types/js-yaml":"^4.0.9"},"_npmOperationalInternal":{"tmp":"tmp/hermes-bridge_0.1.3_1782900854247_0.8829061132181004","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@aidalinfo/hermes-bridge","version":"0.1.4","description":"Relais MCP pour la communication synchrone entre agents Hermes.","type":"module","license":"MIT","bin":{"hermes-bridge":"dist/cli/bin.js"},"scripts":{"build":"tsc","dev":"tsx src/server/index.ts","lint":"tsc --noEmit","test":"vitest run"},"dependencies":{"@modelcontextprotocol/sdk":"^1.29.0","js-yaml":"^4.1.0","langfuse":"^3.38.20","pg":"^8.22.0","ws":"^8.18.0","zod":"^4.4.3"},"devDependencies":{"@types/js-yaml":"^4.0.9","@types/node":"^22.10.0","@types/pg":"^8.20.0","@types/ws":"^8.5.13","tsx":"^4.19.0","typescript":"^5.7.0","vitest":"^2.1.0"},"_id":"@aidalinfo/hermes-bridge@0.1.4","gitHead":"20a91c4d750ffe1882d19eb893d48b0837ca265b","_nodeVersion":"22.23.1","_npmVersion":"10.9.8","dist":{"integrity":"sha512-00NJzaOeX+kW0l3EnLLegk6AJetlFASDXVkdkO7CV0yUdHt0Vw2e6IXpoGFEtwaaMJDrYtElBuxuAB9GdhyP1w==","shasum":"37920dca2da7faa4cdf97b990f8e83904f21b64d","tarball":"https://registry.npmjs.org/@aidalinfo/hermes-bridge/-/hermes-bridge-0.1.4.tgz","fileCount":19,"unpackedSize":67274,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBtKqm3XywbMB9P9XcZswCIkys1gt8Sf8oHcl1Ra4yihAiEAw3Hxu/EpmhbjPpBl/qJAJGcYAg7wfwJbw4Mhp6Vh6f8="}]},"_npmUser":{"name":"aidalinfo","email":"dev@pulsemyit.fr"},"directories":{},"maintainers":[{"name":"aidalinfo","email":"dev@pulsemyit.fr"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/hermes-bridge_0.1.4_1783672664696_0.8552777555817104"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-30T20:40:32.948Z","modified":"2026-07-10T08:37:44.991Z","0.1.0":"2026-06-30T20:40:33.270Z","0.1.1":"2026-07-01T08:27:57.126Z","0.1.2":"2026-07-01T09:28:57.078Z","0.1.3":"2026-07-01T10:14:14.378Z","0.1.4":"2026-07-10T08:37:44.893Z"},"license":"MIT","description":"Relais MCP pour la communication synchrone entre agents Hermes.","maintainers":[{"name":"aidalinfo","email":"dev@pulsemyit.fr"}],"readme":"# hermes-bridge\n\nRelais MCP pour la communication synchrone et multi-tour entre agents Hermes\n(framework Nous Research). Un bot Hermes peut déléguer une tâche ou poser une\nquestion à un autre bot Hermes connu du relais et attendre sa réponse — sans\ndépendre d'un service tiers (pas de Teams, pas de ntfy, pas de Raft).\n\n## Architecture\n\n```\n  bot A (adapter)                    relais (src/server/)                  bot B (adapter)\n  ──────────────                     ────────────────────                  ──────────────\n  tool ask_agent(to=B,...) ───HTTP/MCP──▶ handleAskAgent                     \n                                       │  registry.has(B)?\n                                       │  ConversationStore.createRequest ── wake JSON ──WS──▶ _on_wake()\n                                       │  (request_id, timer=ask_timeout_ms)                    │\n                                       │                                                        │ tour d'inférence\n                                       │                       ◀── heartbeat {request_id} ──WS── │ (post_tool_call/\n                                       │  extendRequest (ré-arme le timer)                       │  post_llm_call)\n                                       │                                                        │\n                                       │  ◀── tool reply(request_id, answer) ───HTTP/MCP────────┘\n  ask_agent() se résout ◀── answer ────┘  resolveRequest\n```\n\n- **Le relais** (`src/server/`) expose trois tools MCP — `ask_agent`,\n  `reply`, `list_agents` — sur `mcp_servers` (transport HTTP), et un endpoint\n  WebSocket (`/bridge/connect`) que chaque bot rejoint en sortant (jamais\n  l'inverse : le relais n'a besoin d'aucun accès réseau vers les bots).\n- **L'adapter** (`adapter/`) est un plugin \"platform\" Hermes installé dans\n  `/opt/data/plugins/hermes-bridge/` de chaque bot — il réveille\n  l'agent (déclenche un tour d'inférence) quand un message arrive, sans\n  toucher au core Hermes ni nécessiter un rebuild d'image.\n  - ⚠️ Le chemin compte : c'est `<HERMES_HOME>/plugins/<name>/`, **pas**\n    `<HERMES_HOME>/.hermes/plugins/<name>/`. `get_hermes_home()` (Hermes)\n    n'ajoute `.hermes` que quand `HERMES_HOME` est *absent* (défaut natif\n    `~/.hermes`) — l'image Docker des bots fixe `HERMES_HOME=/opt/data`\n    explicitement, donc le dossier de scan réel est `/opt/data/plugins`.\n    `npx @aidalinfo/hermes-bridge install` gère ça correctement depuis la\n    0.1.1 ; si un bot a été installé avant, relancer `install` pour corriger\n    l'emplacement, puis redémarrer le conteneur.\n- **`ask_agent`** bloque jusqu'à ce que l'agent cible appelle `reply`, ou\n  jusqu'au timeout (défaut 120s, configurable via `ask_timeout_ms`). Réutiliser\n  le même `conversation_id` permet un échange multi-tour séquentiel ; Hermes\n  conserve l'historique automatiquement via son `chat_id` de session.\n- **Timeout intelligent (heartbeat)** : `ask_timeout_ms` n'est qu'un filet de\n  sécurité contre un agent réellement bloqué/planté, pas une estimation à\n  deviner pour les réponses lentes (plusieurs tool calls, lookup mémoire…).\n  L'adapter de l'agent **cible** s'abonne aux hooks Hermes `pre_llm_call` /\n  `post_tool_call` / `post_llm_call` (les mêmes points d'extension que le\n  statut « busy » natif de Hermes, et le même pattern que l'adapter `raft`\n  bundlé). Tant que la session ouverte par le wake est active, l'adapter\n  envoie une frame `{\"type\":\"heartbeat\",\"request_id\":\"...\"}` sur la **même\n  connexion WebSocket sortante** (pas un nouveau canal), throttlée à 1 toutes\n  les 5s par session. Le relais (`ConversationStore.extendRequest`) ré-arme\n  alors le timer de ce `request_id` pour une fenêtre complète. `on_session_end`\n  nettoie le suivi quand le tour se termine. Résultat : le délai ne compte\n  vraiment que si l'agent s'est *arrêté* de travailler, pas s'il est juste lent.\n  - ⚠️ **Le point piégeux** : les hooks Hermes exposent `session_id =\n    agent.session_id`, un identifiant généré à chaque run d'agent\n    (`f\"{timestamp}_{uuid}\"`) — **sans aucun rapport** avec la clé de session\n    que l'adapter calcule lui-même pour le routage\n    (`gateway.session.build_session_key`, utilisée pour la queue de wakes,\n    jamais exposée aux hooks). Impossible donc de précalculer la\n    correspondance `session_id → request_id` au moment du wake. La solution :\n    `wake.build_wake_text()` embarque déjà `request_id=<id>` en clair dans le\n    texte injecté ; le hook `pre_llm_call` (seul à fournir à la fois\n    `session_id` et `user_message`) relit cet identifiant dans le texte\n    (`wake.extract_request_id`) et fixe la correspondance à ce moment précis —\n    les `post_tool_call`/`post_llm_call` suivants du même run la réutilisent.\n    Autre piège du même hook : `platform` y est le membre d'enum\n    `gateway.config.Platform` (pas une chaîne) — `platform_value()` le\n    déballe avant toute comparaison, sans quoi le filtre `== \"hermes-bridge\"`\n    est toujours faux. Sans ces deux corrections, le heartbeat ne se déclenche\n    *jamais* (échec silencieux — aucune erreur, juste des frames qui ne\n    partent jamais), et `ask_timeout_ms` reste un mur fixe malgré un adapter\n    et un relais à jour.\n  - ⚠️ Ce mécanisme est **entièrement côté adapter + relais** — aucune action\n    requise de l'agent/LLM cible (il ne « sait » même pas que ça existe).\n  - ⚠️ **Le relais doit être redéployé** pour que le heartbeat fonctionne :\n    publier une nouvelle version npm de l'adapter ne suffit pas, le serveur\n    (`src/server/bridge-ws.ts` + `conversations.ts`) doit tourner avec le code\n    à jour pour comprendre les frames `heartbeat`.\n\nDétails de conception complets : voir `manageai/docs/superpowers/specs/2026-06-30-hermes-bridge-design.md`.\n\n## Déployer le relais\n\n```bash\ndocker build -t hermes-bridge .\ndocker run -d -p 8787:8787 -v $(pwd)/config.yaml:/app/config.yaml:ro hermes-bridge\n```\n\n`config.yaml` (voir `config.example.yaml`) :\n\n```yaml\nagents:\n  - name: daniel-bot\n    token: <token-secret-par-bot>\n  - name: helpdesk-bot\n    token: <token-secret-par-bot>\nask_timeout_ms: 120000\n```\n\n## Installer l'adapter sur un bot\n\n```bash\ndocker exec -it -u hermes <bot> npx @aidalinfo/hermes-bridge install \\\n  --token=<token-du-bot> \\\n  --relay-url=wss://<host-du-relais>/bridge/connect\n```\n\nPuis redémarrer le conteneur du bot pour charger le plugin.\n\n## Ajouter le relais aux `mcp_servers` du bot\n\n```yaml\nmcp_servers:\n  hermes-bridge:\n    enabled: true\n    transport: http\n    url: https://<host-du-relais>/mcp\n    headers:\n      Authorization: Bearer ${HERMES_BRIDGE_TOKEN}\n    access_mode: read_write\n```\n\n## Persistance (mode db)\n\nPar défaut, l'historique des échanges vit en mémoire (`maxHistory=200`,\n`telemetry.ts`) et **disparaît à chaque redémarrage du relais** — y compris\nun redeploy Coolify normal sur push. Pour une traçabilité durable (audit,\n« qu'est-ce que daniel-bot a répondu à helpdesk-bot mardi dernier ? »),\nconfigurez une base — seul postgres est implémenté, et c'est le driver par\ndéfaut :\n\n```yaml\ndb:\n  driver: postgres              # défaut si omis\n  connection_string: postgresql://user:pass@host:5432/hermes_bridge\n```\n\n`connection_string` peut aussi venir de la variable d'env `DATABASE_URL`\n(recommandé — évite de committer un secret dans `config.yaml` ; dans ce cas\nle bloc `db:` peut être omis entièrement). Le mode db s'active dès que\n`config.db.connection_string` **ou** `DATABASE_URL` est renseigné.\n\nCe que ça change concrètement :\n\n- La table `hermes_bridge_exchanges` est créée automatiquement au démarrage\n  (`src/server/db.ts`, `CREATE TABLE IF NOT EXISTS`) — aucune migration\n  manuelle.\n- Chaque `recordStart`/`recordEnd` écrit dans la base **en plus** de la\n  mémoire, en fire-and-forget (comme l'export Langfuse existant) : une\n  panne db ne bloque jamais un `ask_agent`/`reply`, juste un `console.warn`\n  (throttlé à une fois).\n- **`/ui` et `/ui/api/state` lisent depuis la base** quand le mode db est\n  actif (pas depuis la mémoire) — c'est ce qui les rend durables : le flux\n  affiché après un redémarrage n'est plus vide, il reprend l'historique.\n  En cas d'échec de lecture db, repli silencieux sur la mémoire (mieux\n  vaut un historique tronqué qu'une page cassée).\n- Sans `db` configuré, comportement strictement inchangé (mémoire\n  uniquement, comme avant cette fonctionnalité).\n\n## Observabilité\n\nChaque échange `ask_agent` → `reply` (ou timeout/déconnexion) peut être\nexporté vers une instance [Langfuse](https://langfuse.com/) existante\n(cloud ou self-hosted), regroupé par `conversation_id` — les échanges\nmulti-tours d'une même conversation apparaissent comme plusieurs spans\nd'une seule trace :\n\n```yaml\nlangfuse:\n  public_key: pk-lf-...\n  secret_key: sk-lf-...\n  base_url: https://cloud.langfuse.com   # optionnel, défaut cloud Langfuse\n```\n\nSans cette section, le relais fonctionne normalement sans appel réseau vers\nLangfuse. Langfuse et le mode db sont indépendants — Langfuse pour tracer\nen externe, le mode db pour l'audit local/`/ui` durable — activables\nséparément ou ensemble.\n\nLe relais expose aussi une page `/ui` (ex: `http://<host-du-relais>:8787/ui`),\n**« Conversations entre agents »** — layout et styles Forma importés du\nprojet Claude Design\n[`Visualiser les conversations d'agents`](https://claude.ai/design/p/2463da63-90c9-4f82-9afd-d2011605f90c?file=Agent+Conversations.dc.html)\n(voir `src/server/ui.ts`, réimplémenté en HTML/JS sans dépendance, branché sur\nles vraies données au lieu des exemples du prototype) :\n\n- Un badge par agent connu (en ligne / hors ligne, point de couleur), rangée\n  du haut.\n- Une recherche texte (message + réponse/erreur) et un filtre par agent.\n- Un flux des échanges les plus récents en premier, chacun avec `from → to`,\n  durée, badge de statut (`ok`, `timeout`, `agent hors ligne`,\n  `agent déconnecté`, `agent inconnu`, `conversation inconnue`, `en cours`),\n  message tronqué à 180 caractères avec un bouton **Voir plus/moins** qui\n  révèle la réponse (ou « En attente de réponse… » tant que c'est `pending`).\n- Rafraîchissement automatique (`fetch('/ui/api/state')` toutes les 3s) sans\n  perdre la recherche/le filtre/les échanges dépliés en cours.\n\nCette page **n'est pas authentifiée** — elle affiche le contenu intégral des\nmessages/réponses. Si le relais est exposé au-delà d'un LAN de confiance,\nmettez-la derrière un reverse-proxy protégé.\n\n## Développement\n\n```bash\nnpm install\nnpm test            # tests TypeScript (vitest)\npytest adapter/test # tests Python (wake.py — logique pure, sans dépendance Hermes)\nnpm run dev          # lance le relais localement (HERMES_BRIDGE_CONFIG, PORT)\n```\n","readmeFilename":"README.md"}