{"_id":"@emiltsoi/openclaw-mesh","_rev":"4-1cbdc95e96fe4bda68fa5d6a68439210","name":"@emiltsoi/openclaw-mesh","dist-tags":{"latest":"0.2.6"},"versions":{"0.2.1":{"name":"@emiltsoi/openclaw-mesh","version":"0.2.1","author":{"name":"emiltsoi"},"_id":"@emiltsoi/openclaw-mesh@0.2.1","maintainers":[{"name":"emiltsoi","email":"tsoiyanhung@gmail.com"}],"dist":{"shasum":"5765322d3cf1322467e4562697bf866c21a35a2d","tarball":"https://registry.npmjs.org/@emiltsoi/openclaw-mesh/-/openclaw-mesh-0.2.1.tgz","fileCount":30,"integrity":"sha512-YHppbfGcSvj+VGyvGUbN02943NWC7fqSbb6qXyS3RBlYWQ1SA6A+xOrll+AhCJTyKiPYsaqGJBEIqPYx4XgDhQ==","signatures":[{"sig":"MEUCIHH1jFKKzU9r8c9fO3oVDo0yPHA7whcxUW8egbVZQbVkAiEA6YyIZrR7OQ0C46tXDtnQNw51jaoFwLpmt5Goa+GXRDY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":123872},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":"./dist/index.js"},"gitHead":"28d9b72d6e4975e1a5e7485547ebfc0390ffca43","scripts":{"test":"tsc -p tsconfig.test.json && node --test dist-test/test/*.test.js","build":"tsc","test:unit":"npm run test -- --test-name-pattern=unit","typecheck":"tsc --noEmit","clawhub-publish":"node scripts/clawhub-publish.js"},"_npmUser":{"name":"emiltsoi","email":"tsoiyanhung@gmail.com"},"openclaw":{"build":{"openclawVersion":"2026.6.11","pluginSdkVersion":"2026.6.11"},"compat":{"pluginApi":">=2026.6.11","minGatewayVersion":">=2026.6.11"},"extensions":["./dist/index.js"]},"_npmVersion":"11.18.0","description":"Stateful, Ed25519-signed agent-to-agent mesh messaging for OpenClaw with Hermes bridge support","directories":{},"_nodeVersion":"26.6.0","dependencies":{"js-yaml":"^5.2.2"},"_hasShrinkwrap":false,"devDependencies":{"openclaw":"*","typescript":"^5.0.0","@types/node":"*","@types/js-yaml":"^4.0.9"},"peerDependencies":{"openclaw":">=2026.6.11"},"_npmOperationalInternal":{"tmp":"tmp/openclaw-mesh_0.2.1_1785925286960_0.625392877145823","host":"s3://npm-registry-packages-npm-production"}},"0.2.3":{"name":"@emiltsoi/openclaw-mesh","version":"0.2.3","author":{"name":"emiltsoi"},"_id":"@emiltsoi/openclaw-mesh@0.2.3","maintainers":[{"name":"emiltsoi","email":"tsoiyanhung@gmail.com"}],"homepage":"https://github.com/emiltsoi/openclaw-mesh#readme","bugs":{"url":"https://github.com/emiltsoi/openclaw-mesh/issues"},"dist":{"shasum":"7e1012ce4d2498184715706006ca393aa1197264","tarball":"https://registry.npmjs.org/@emiltsoi/openclaw-mesh/-/openclaw-mesh-0.2.3.tgz","fileCount":30,"integrity":"sha512-XfSoq7XNLOBsCiQ3pWFIDjoPyv6xgrzao/WqgdGFMmfhiHn9dSffT67VYLBi8KIvIVq0Q1BMwet330NyhXhw+w==","signatures":[{"sig":"MEUCIQCDJuXxEVe/qfmONLgukkDehNGRFlQtq83489IZnZssTwIgY28UecH0kjI68989kJ3U0Iwapfff2tM/LrclhE2jjWg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@emiltsoi%2fopenclaw-mesh@0.2.3","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":135163},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":"./dist/index.js"},"gitHead":"560f76d88f33b73d764c6c483e94e1e1dc889f79","scripts":{"test":"tsc -p tsconfig.test.json && node --test dist-test/test/*.test.js","build":"tsc","test:unit":"npm run test -- --test-name-pattern=unit","typecheck":"tsc --noEmit","clawhub-publish":"node scripts/clawhub-publish.js"},"_npmUser":{"name":"emiltsoi","email":"tsoiyanhung@gmail.com"},"openclaw":{"build":{"openclawVersion":"2026.6.11","pluginSdkVersion":"2026.6.11"},"compat":{"pluginApi":">=2026.6.11","minGatewayVersion":">=2026.6.11"},"extensions":["./dist/index.js"]},"repository":{"url":"git+https://github.com/emiltsoi/openclaw-mesh.git","type":"git"},"_npmVersion":"10.9.8","description":"Stateful, Ed25519-signed agent-to-agent mesh messaging for OpenClaw with Hermes bridge support","directories":{},"_nodeVersion":"22.23.2","dependencies":{"js-yaml":"^5.2.2"},"_hasShrinkwrap":false,"devDependencies":{"openclaw":"*","typescript":"^5.0.0","@types/node":"*","@types/js-yaml":"^4.0.9"},"peerDependencies":{"openclaw":">=2026.6.11"},"_npmOperationalInternal":{"tmp":"tmp/openclaw-mesh_0.2.3_1786881727674_0.7481700173134171","host":"s3://npm-registry-packages-npm-production"}},"0.2.4":{"name":"@emiltsoi/openclaw-mesh","version":"0.2.4","author":{"name":"emiltsoi"},"_id":"@emiltsoi/openclaw-mesh@0.2.4","maintainers":[{"name":"emiltsoi","email":"tsoiyanhung@gmail.com"}],"homepage":"https://github.com/emiltsoi/openclaw-mesh#readme","bugs":{"url":"https://github.com/emiltsoi/openclaw-mesh/issues"},"dist":{"shasum":"644c1e0bc64f27d84b676b074d7c35c9daa665e7","tarball":"https://registry.npmjs.org/@emiltsoi/openclaw-mesh/-/openclaw-mesh-0.2.4.tgz","fileCount":30,"integrity":"sha512-UzYyV8zyL46tYjEEtvwQcTKgMlCtJHesbDEIHp+3Hcg5nyRCkMygiPkrMEhUZ7XVkC2Fe3ZrDM5gG8P7Ds3TSg==","signatures":[{"sig":"MEUCIQCM0dOSJSIGwRJ2TazwqoCj5nQyIHuNm3egQTL0hoFadgIgbUx/t9WQl6CQKRzQzrowqE4KsfT7Zv19hue3Hj+4cnA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@emiltsoi%2fopenclaw-mesh@0.2.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":137238},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","engines":{"node":">=20"},"exports":{".":"./dist/index.js"},"gitHead":"c67a754f223cd79560ec0eec74fb78c285e30362","scripts":{"test":"tsc -p tsconfig.test.json && node --test dist-test/test/*.test.js","build":"tsc","test:unit":"npm run test -- --test-name-pattern=unit","typecheck":"tsc --noEmit","clawhub-publish":"node scripts/clawhub-publish.js"},"_npmUser":{"name":"emiltsoi","email":"tsoiyanhung@gmail.com"},"openclaw":{"build":{"openclawVersion":"2026.6.11","pluginSdkVersion":"2026.6.11"},"compat":{"pluginApi":">=2026.6.11","minGatewayVersion":">=2026.6.11"},"extensions":["./dist/index.js"]},"repository":{"url":"git+https://github.com/emiltsoi/openclaw-mesh.git","type":"git"},"_npmVersion":"10.9.8","description":"Stateful, Ed25519-signed agent-to-agent mesh messaging for OpenClaw with Hermes bridge support","directories":{},"_nodeVersion":"22.23.2","dependencies":{"js-yaml":"^5.2.2"},"_hasShrinkwrap":false,"devDependencies":{"openclaw":"*","typescript":"^5.0.0","@types/node":"*","@types/js-yaml":"^4.0.9"},"peerDependencies":{"openclaw":">=2026.6.11"},"_npmOperationalInternal":{"tmp":"tmp/openclaw-mesh_0.2.4_1786889991173_0.8232895931042894","host":"s3://npm-registry-packages-npm-production"}},"0.2.6":{"name":"@emiltsoi/openclaw-mesh","version":"0.2.6","repository":{"type":"git","url":"git+https://github.com/emiltsoi/openclaw-mesh.git"},"description":"Stateful, Ed25519-signed agent-to-agent mesh messaging for OpenClaw with Hermes bridge support","author":{"name":"emiltsoi"},"type":"module","main":"./dist/index.js","exports":{".":"./dist/index.js"},"scripts":{"build":"tsc","typecheck":"tsc --noEmit","test":"tsc -p tsconfig.test.json && node --test dist-test/test/*.test.js","test:unit":"npm run test -- --test-name-pattern=unit","clawhub-publish":"node scripts/clawhub-publish.js"},"dependencies":{"js-yaml":"^5.2.2"},"peerDependencies":{"openclaw":">=2026.6.11"},"devDependencies":{"@types/js-yaml":"^4.0.9","@types/node":"*","openclaw":"*","typescript":"^5.0.0"},"engines":{"node":">=20"},"openclaw":{"extensions":["./dist/index.js"],"compat":{"pluginApi":">=2026.6.11","minGatewayVersion":">=2026.6.11"},"build":{"openclawVersion":"2026.6.11","pluginSdkVersion":"2026.6.11"}},"_id":"@emiltsoi/openclaw-mesh@0.2.6","gitHead":"292ec03ea9fc7d16163c0c158175e9e003e5b3d4","types":"./dist/index.d.ts","bugs":{"url":"https://github.com/emiltsoi/openclaw-mesh/issues"},"homepage":"https://github.com/emiltsoi/openclaw-mesh#readme","_nodeVersion":"22.23.2","_npmVersion":"10.9.8","dist":{"integrity":"sha512-/frK0JUSH8edannFnlbB9wrJCKhmJ6K8c+iAukhS2rPi/wgknjyG1AQ2FOL0QhtdoKXVGn1V6Q4qRC5n5oXz8g==","shasum":"603b6600155515e88fc84c0615726f70061f4094","tarball":"https://registry.npmjs.org/@emiltsoi/openclaw-mesh/-/openclaw-mesh-0.2.6.tgz","fileCount":35,"unpackedSize":163890,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@emiltsoi%2fopenclaw-mesh@0.2.6","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDWMxN2kFFlriN3lKWW3BuRatphJiP917SyCPd6Tkw2iAIhAJcr9SFCwjRcQieOHQqMJv/cNLJMgGWLMYMpoQahscjG"}]},"_npmUser":{"name":"emiltsoi","email":"tsoiyanhung@gmail.com"},"directories":{},"maintainers":[{"name":"emiltsoi","email":"tsoiyanhung@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/openclaw-mesh_0.2.6_1788543308601_0.3621789443410821"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-05T10:21:26.535Z","modified":"2026-09-04T17:35:09.047Z","0.2.1":"2026-08-05T10:21:27.124Z","0.2.3":"2026-08-16T12:02:07.815Z","0.2.4":"2026-08-16T14:19:51.340Z","0.2.6":"2026-09-04T17:35:08.740Z"},"bugs":{"url":"https://github.com/emiltsoi/openclaw-mesh/issues"},"author":{"name":"emiltsoi"},"homepage":"https://github.com/emiltsoi/openclaw-mesh#readme","repository":{"type":"git","url":"git+https://github.com/emiltsoi/openclaw-mesh.git"},"description":"Stateful, Ed25519-signed agent-to-agent mesh messaging for OpenClaw with Hermes bridge support","maintainers":[{"name":"emiltsoi","email":"tsoiyanhung@gmail.com"}],"readme":"# openclaw-mesh\n\nStateful, Ed25519-signed agent-to-agent mesh messaging for OpenClaw. Use it to let one OpenClaw agent send messages to another OpenClaw agent, or to bridge OpenClaw agents to [Hermes](https://github.com/emiltsoi/hermes-mesh) mesh peers. All traffic is carried over the `[mesh]` envelope format with Ed25519 signatures, durable inbox persistence, and SSRF-protected outbound delivery.\n\n## What it does\n\n`openclaw-mesh` turns an OpenClaw agent into a mesh peer:\n\n- **Inbound:** receives a `[mesh]` webhook, verifies the sender's Ed25519 `X-Mesh-Signature`, writes the message to a durable inbox, and triggers an in-process OpenClaw agent turn in the configured session.\n- **Outbound:** exposes the `mesh_send` tool so the agent can Ed25519-sign and POST `[mesh]` envelopes to any peer discovered from the shared mesh vault.\n- **Discovery:** exposes `mesh_list`, `mesh_register`, `mesh_deregister`, `mesh_sync`, and `mesh_publish` so agents can discover, register, and deregister themselves without leaving the chat.\n\nBecause both OpenClaw and Hermes agents can share the same mesh vault and envelope format, the plugin works in two modes:\n\n1. **OpenClaw-only mesh** — two or more OpenClaw agents register in the same vault and send `[mesh]` envelopes to each other.\n2. **Hermes bridge** — OpenClaw agents exchange envelopes with Hermes mesh peers.\n\n## Architecture\n\n```\nOpenClaw agent A                     OpenClaw agent B (this plugin)\n    │ mesh_send(to=B)                    │\n    │  [mesh] envelope + Ed25519 sig     │\n    └───────────────webhook──────────────▶│\n                                           ├─ verify Ed25519 against sender's public_key\n                                           ├─ write to mesh-inbox.jsonl\n                                           ├─ optional mirror (telegram/cli)\n                                           └─ runEmbeddedAgent(target session)\n```\n\nThe same flow works when the sender is a Hermes mesh agent.\n\nThe plugin:\n\n1. Verifies the inbound `X-Mesh-Signature` and `X-Mesh-Timestamp` headers using the sender's cached `public_key` from the mesh vault (falling back to the optional `mesh-peer-registry`).\n2. Parses and validates the `[mesh][from:...][to:...][id:...][action:...][reply:...]` envelope (plus the optional `[session:...]` / `[from_session:...]` tokens from the session-selector cut).\n3. Writes the message to a durable inbox (`/tmp/openclaw-mesh/mesh-inbox.jsonl` by default).\n4. Optionally mirrors the inbound message to `telegram` or `cli`.\n5. Resolves the real session UUID for the target session key (OpenClaw 2.0 sqlite `session_nodes.current_session_id`), wraps the embedded run in the gateway's independent root-work admission (`runWithGatewayIndependentRootWorkAdmission`), and calls `api.runtime.agent.runEmbeddedAgent(...)` so the configured session wakes and processes the turn.\n6. Mirrors the embedded run's final assistant output to Telegram (when `mirrorOutbound: \"telegram\"`) instead of auto-replying to the envelope sender — matching Hermes-side behavior.\n\n## OpenClaw 2.0 change set\n\nOpenClaw 2.0 (2026.8.x) moved session storage from JSONL files to a sqlite store (`session_nodes`, `session_windows`). This changed the embedded-run contract in three ways, all handled by the plugin:\n\n1. **Session UUID resolution (writer admission).** The 2.0 runtime's writer admission (`claimAgentSessionWriter`) compares the `sessionId` passed to `runEmbeddedAgent` against the store entry's real session UUID (`session_nodes.current_session_id`). Passing a key-derived id (e.g. `'main'` from `agent:main:main`) now fails with `Session changed before writer admission`. The plugin resolves the real UUID from the store before the run (`resolveSessionIdForRun`), falling back to the key-derived id on pre-2.0 (JSONL-era) stores for backward compatibility.\n\n2. **Key-tolerant fallback (defense-in-depth).** When the stored session id is not UUID-shaped (a legacy JSONL-era row that survived migration), `resolveSessionIdForRun` returns the full session key rather than forcing a UUID into the admission check. This does **not** by itself fix 2.0 admission — the real fix for a key-shaped legacy session is minting a fresh UUID via the runtime's own `createSessionEntryWithTranscript` — but it keeps the plugin from making a bad situation worse on any future key-shaped row.\n\n3. **Root-work admission wrap (the \"Gateway is draining\" saga).** The 2.0 runtime only admits *subordinate* (root-less) work when the gateway's suspend phase is `accepting` — which is why embedded mesh runs fired fire-and-forget failed intermittently between turns (and why Telegram turns, which always hold a gateway root, never failed). The plugin now wraps every embedded run in `runWithGatewayIndependentRootWorkAdmission`, giving the run its own independent root — the same wrapper the runtime itself uses for delivery-queue drains, heartbeat wakes, and restart recovery. This makes embedded runs reliable between turns, not just during the post-boot accepting window.\n\n### Telegram mirror behavior\n\nSince 0.2.5, embedded-run output is **mirrored to Telegram** (when `mirrorOutbound: \"telegram\"` is configured) instead of auto-replying to the envelope sender. This matches Hermes-side behavior: the mesh message arrives, the agent processes it in its session, and the visible output lands in the agent's Telegram chat — not as a mesh reply to the sender. The `reply:` envelope field remains respected for delivery receipts.\n\n## Installation\n\nPublished on ClawHub:\n\n```bash\nopenclaw plugins install clawhub:@emiltsoi/openclaw-mesh\n```\n\nOr install from the local checkout for development:\n\n```bash\ncp -r /path/to/openclaw-mesh ~/.openclaw/workspaces/<agent>/plugins/openclaw-mesh\nnpm install\nnpm run build\n```\n\nThen enable it in `~/.openclaw/workspaces/<agent>/openclaw-<agent>.json`:\n\n```json\n{\n  \"plugins\": {\n    \"load\": {\n      \"paths\": [\n        \"/home/emil/.openclaw/workspaces/<agent>/plugins/openclaw-mesh\"\n      ]\n    },\n    \"entries\": {\n      \"openclaw-mesh\": {\n        \"enabled\": true,\n        \"config\": {\n          \"routingAgent\": \"emts\",\n          \"targetSessionKey\": \"agent:main:main\",\n          \"targetAgentId\": \"main\",\n          \"sourceChannel\": \"mesh\",\n          \"sourceTo\": \"\",\n          \"meshVaultPath\": \"\",\n          \"mirrorInbound\": \"none\",\n          \"mirrorOutbound\": \"none\",\n          \"debug\": false,\n          \"allowLoopback\": false,\n          \"deliveryRetries\": 3,\n          \"deliveryBackoffMs\": 1000,\n          \"deliveryTimeoutMs\": 15000\n        }\n      }\n    }\n  }\n}\n```\n\nRestart the OpenClaw gateway after changing source or `openclaw.plugin.json`.\n\n## Configuration Options\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `routingAgent` | `emts` | Mesh agent name this instance accepts messages for. |\n| `targetSessionKey` | `agent:main:main` | Target OpenClaw session key. |\n| `targetAgentId` | `main` | Target OpenClaw agent ID. |\n| `sourceChannel` | `mesh` | Channel attributed to the injected turn. |\n| `sourceTo` | `envelope.from` | Channel target/to for the injected turn. |\n| `model` | `config.agents.defaults.model.primary` or `deepseek/deepseek-v4-pro` | Optional `provider/model` override for the embedded run. |\n| `meshVaultPath` | `$OPENCLAW_STATE_DIR/mesh` or `/tmp/openclaw-mesh` | Path to the mesh vault root (the directory that contains `mesh/agents`). |\n| `inboxPath` | `/tmp/openclaw-mesh/mesh-inbox.jsonl` | Durable inbox file path. |\n| `mirrorInbound` | `none` | Where to mirror inbound mesh messages: `none`, `telegram`, or `cli`. |\n| `mirrorOutbound` | `none` | Where to mirror outbound mesh messages: `none`, `telegram`, or `cli`. |\n| `debug` | `false` | Emit verbose debug logs to stderr and `/tmp/openclaw-mesh-debug.log`. |\n| `allowLoopback` | `false` | Allow outbound webhook deliveries to loopback/private addresses. |\n| `privateNetworkPolicy` | `deny` | Override for private network handling. Set to `allow`, `warn`, or `deny`. If `allowLoopback` is `true`, loopback deliveries are allowed regardless of this value. |\n| `deliveryRetries` | `3` | Number of outbound webhook delivery attempts. |\n| `deliveryBackoffMs` | `1000` | Initial retry backoff in milliseconds. |\n| `deliveryTimeoutMs` | `15000` | Per-attempt delivery timeout in milliseconds. |\n| `registryUrl` | — | URL of the mesh-peer-registry server (e.g. `https://registry.example.com`). Used by `mesh_sync` and `mesh_publish`. |\n| `privateKeyPath` | `~/.mesh/keys/<routingAgent>.pem` | Path to the Ed25519 private key PEM. Generated on first use if missing. |\n| `signTimestamp` | `true` | Include `X-Mesh-Timestamp` in the signed outbound payload. |\n\n> **Migration note (v0.2.2):** timestamp-covered signatures are now **required** on\n> inbound verification. The body-only signature fallback was removed (U12), so any\n> legacy peer with `signTimestamp: false` must migrate to signing `\"<ts>\\n<body>\"`\n> (the `X-Mesh-Timestamp` header value followed by a newline, then the exact request\n> body). We own both ends of the mesh, so the fallback was dropped rather than kept.\n| `allowInsecureRegistry` | `false` | Allow `http://` registry URLs. Not recommended for production. |\n| `registryPin` | — | SHA-256 hex digest of the registry server certificate SPKI for TLS pinning. |\n| `auditLogPath` | — | Optional JSON-lines audit log file for mesh traffic. Falls back to `OPENCLAW_MESH_AUDIT_LOG`. |\n\nThe Ed25519 private key is loaded in this order:\n\n1. `pluginConfig.privateKeyPath`\n2. `MESH_PRIVATE_KEY_PATH` environment variable\n3. `~/.mesh/keys/<routingAgent>.pem` (generated on first use)\n\n## Inbound / Outbound Mirroring\n\nMirroring lets you observe mesh traffic without opening the session transcript. It is controlled independently per direction:\n\n- `mirrorInbound` — applied when a mesh webhook is received.\n- `mirrorOutbound` — applied when `mesh_send` posts to a peer webhook.\n\nSupported values:\n\n| Value | Behaviour |\n|-------|-----------|\n| `none` | No mirroring (default). |\n| `telegram` | Send via the Telegram Bot API using `config.channels.telegram.botToken` / `chatId`. |\n| `cli` | Write to `stdout`, which appears in the gateway logs. |\n\n## Mesh Vault Discovery\n\nThe plugin can discover peers from a **file-based mesh vault** (default) or from an optional [`mesh-peer-registry`](https://github.com/emiltsoi/mesh-peer-registry) server.\n\n```\n$OPENCLAW_STATE_DIR/mesh/mesh/agents/\n├── agent0/\n│   └── identity.yaml\n├── emts/\n│   └── identity.yaml\n└── linda/\n    └── identity.yaml\n```\n\nFive tools are exposed:\n\n- **`mesh_list`** — list discoverable peers with `name`, `platform`, `a2a_url`, `webhook_url`, and `public_key`. No secrets are leaked.\n- **`mesh_send(agent, message, action?, reply?, id?, thread_id?)`** — resolve a peer, Ed25519-sign a `{\"from\": \"<routingAgent>\", \"text\": \"[mesh][from:...]...\"}` payload with the sender's private key, and POST it to the peer's `hermes_webhook` URL. Use `id` or `thread_id` to preserve the mesh thread id on replies.\n- **`mesh_register(name?, description?, role?, platform?, a2a_url?, webhook_url?, public_key?, allow_loopback?)`** — write or update this agent's `identity.yaml` in the mesh vault so peers can discover it. Defaults are derived from `routingAgent`, the OpenClaw gateway config, and a generated Ed25519 keypair.\n- **`mesh_deregister(name?, force?)`** — remove this agent from the local vault and, if configured, from the mesh-peer-registry.\n- **`mesh_sync(name?, registry_url?)`** — fetch a peer (or all peers) from the mesh-peer-registry and cache it in the local vault.\n- **`mesh_publish(name?, url, role?, description?, ttl?, registry_url?)`** — publish this agent's webhook URL and Ed25519 public key to the mesh-peer-registry.\n\n### Agent listing\n\n`mesh_list` returns a JSON object like:\n\n```json\n{\n  \"count\": 2,\n  \"peers\": [\n    {\n      \"name\": \"agent0\",\n      \"platform\": \"hermes\",\n      \"a2a_url\": \"http://127.0.0.1:41808/a2a\",\n      \"webhook_url\": \"http://127.0.0.1:8645/mesh/receive\",\n      \"public_key\": \"-----BEGIN PUBLIC KEY-----\\n...\",\n      \"description\": \"Hermes agent zero\",\n      \"role\": \"operator\"\n    },\n    {\n      \"name\": \"emts\",\n      \"platform\": \"openclaw\",\n      \"a2a_url\": \"http://127.0.0.1:18860\",\n      \"webhook_url\": \"http://127.0.0.1:18860/plugins/openclaw-mesh/webhook\",\n      \"public_key\": \"-----BEGIN PUBLIC KEY-----\\n...\",\n      \"description\": \"OpenClaw mesh peer\",\n      \"role\": \"mesh_peer\"\n    }\n  ]\n}\n```\n\nPrivate keys are never exposed in the listing; they are only used internally when `mesh_send` signs an outbound message.\n\n`meshVaultPath` is path-neutral: `~` and relative paths are resolved through OpenClaw's `api.resolvePath` or manual `~` expansion, so you can point the plugin at any vault on any system. It points to the **mesh vault root** (the directory that contains `mesh/agents`), and the plugin appends `mesh/agents` internally.\n\nIt resolves in this order:\n\n1. `pluginConfig.meshVaultPath` — mesh vault root (supports `~` and relative paths)\n2. `MESH_VAULT_PATH` environment variable — mesh vault root (supports `~` and relative paths)\n3. `HERMES_HOME` (with `/profiles/<name>` stripped) + `/fleet/mesh/agents`\n4. Fallback to `$OPENCLAW_STATE_DIR/mesh` (or `/tmp/openclaw-mesh` if `OPENCLAW_STATE_DIR` is not set)\n\n### Mesh Peer Registry\n\nFor a centralized, multi-host discovery backend you can use [`mesh-peer-registry`](https://github.com/emiltsoi/mesh-peer-registry) (also on [PyPI](https://pypi.org/project/mesh-peer-registry/)):\n\n```bash\npip install mesh-peer-registry\nmesh-peer-registry --port 8646 --store ~/.mesh/registry.sqlite\n```\n\nThen point `openclaw-mesh` at it:\n\n```json\n{\n  \"registryUrl\": \"https://registry.example.com\",\n  \"privateKeyPath\": \"~/.mesh/keys/emts.pem\",\n  \"registryPin\": \"sha256-hex-of-server-certificate-spki\"\n}\n```\n\nThe registry is **optional**: the local vault is the runtime source of truth. `mesh_sync` pulls peers from the registry into the vault, and `mesh_publish` pushes this peer's public key and webhook URL to the registry.\n\nThe registry is language-agnostic: Hermes peers and OpenClaw peers can share the same `mesh-peer-registry` instance. See the [mesh-peer-registry README](https://github.com/emiltsoi/mesh-peer-registry/blob/main/README.md) for API details.\n\n## Registering an OpenClaw agent\n\n### Agent-friendly way: `mesh_register`\n\nThe agent can register itself by calling the `mesh_register` tool. In most cases just call:\n\n```\nmesh_register()\n```\n\nThe plugin fills in:\n\n- `name` from `routingAgent` (or `MESH_AGENT_NAME`, defaulting to `emts`)\n- `a2a_url` from the OpenClaw gateway config (`http://127.0.0.1:<port>`)\n- `webhook_url` as `<a2a_url>/plugins/openclaw-mesh/webhook`\n- `public_key` from a generated or reused Ed25519 keypair at `privateKeyPath`\n\nOptional overrides:\n\n```\nmesh_register(name=\"emts\", description=\"OpenClaw mesh peer\", role=\"mesh_peer\", platform=\"openclaw\")\n```\n\n`mesh_register` is idempotent — calling it again overwrites the same `identity.yaml` with updated values. The vault directory is created with `0o700` permissions and the `identity.yaml` file with `0o600` permissions.\n\n### Manual way\n\nIf you prefer to write the file outside the agent turn, create a directory and `identity.yaml` under `<mesh-vault-root>/mesh/agents/<agent-name>/`:\n\n```bash\nmkdir -p $OPENCLAW_STATE_DIR/mesh/mesh/agents/emts\n```\n\nThen write `$OPENCLAW_STATE_DIR/mesh/mesh/agents/emts/identity.yaml`:\n\n```yaml\nid: emts\nname: emts\nkind: openclaw-agent\nrole: mesh_peer\ndescription: OpenClaw mesh peer\na2a_url: http://127.0.0.1:18860\nwebhook_url: http://127.0.0.1:18860/plugins/openclaw-mesh/webhook\nallow_loopback: true\ntransports:\n  hermes_webhook:\n    protocol: hermes-webhook\n    url: http://127.0.0.1:18860/plugins/openclaw-mesh/webhook\n    auth:\n      public_key: |\n        -----BEGIN PUBLIC KEY-----\n        <sender's-ed25519-public-key>\n        -----END PUBLIC KEY-----\n```\n\nSet `allow_loopback: true` when the peer runs on the same host and you want the plugin to allow deliveries to `127.0.0.1`/private addresses.\n\n## Envelope Format\n\nThe `[mesh]` envelope is shared with `hermes-mesh`:\n\n```\n[mesh][from:<sender>][to:<recipient>][id:<uuid>][action:do|info][reply:yes|no|end] <message>\n```\n\nMessages not addressed to the configured `routingAgent` (or `*`) are silently ignored. Brackets inside the message body are preserved when the envelope header is stripped.\n\n**Terminal replies (`reply=end`):** the bridge accepts and forwards `reply=end`. Terminal-thread enforcement (`THREAD_CLOSED`) and ref requirements are enforced by hermes-mesh: `end` marks a message as terminal and no reply is expected; hermes-mesh expects replies to a terminal message to carry `ref=<anchor>`.\n\n## Security Notes\n\n- **SSRF protection:** outbound deliveries use OpenClaw's `fetchWithSsrFGuard` with per-peer `allow_loopback` and the configurable `allowLoopback` / `privateNetworkPolicy` settings. By default private/loopback targets are rejected. Set `allowLoopback: true` to allow loopback deliveries regardless of the `privateNetworkPolicy` default, or use `privateNetworkPolicy: \"allow\"` / `\"warn\"` for more control. As a break-glass, set `OPENCLAW_MESH_ALLOW_LOOPBACK=1`.\n- **Ed25519 signatures:** outbound messages are signed with the sender's private key. Inbound messages are verified with the sender's cached public key from the mesh vault or the optional mesh-peer-registry.\n- **HMAC removed:** the previous HMAC-SHA256 (`X-Hub-Signature-256`) mode is no longer supported. Existing deployments must re-register agents to generate Ed25519 keys.\n- **Certificate pinning:** when using a registry over HTTPS, set `registryPin` to the SHA-256 hex digest of the server certificate's SPKI, or set `MESH_REGISTRY_PIN`.\n- **Envelope token validation:** `from`, `to`, `id`, `action`, and `reply` fields are validated to keep the header well-formed.\n- **Debug logging:** gated by `config.debug` or `OPENCLAW_MESH_DEBUG`; private keys and tokens are redacted from logs.\n\n## Development\n\n```bash\nnpm run typecheck   # TypeScript type check only\nnpm run build       # Compile src/ → dist/\nnpm test            # Run unit tests with node:test\n```\n\nCI is configured in `.github/workflows/ci.yml` and runs `typecheck`, `build`, and `test` on every push and pull request to `main`.\n\n## Cross-harness mesh\n\nThe same `[mesh]` envelope and Ed25519 wire format works across three harnesses:\n\n| Harness | Mesh bridge |\n|---|---|\n| **Hermes** | [hermes-mesh](https://github.com/emiltsoi/hermes-mesh) |\n| **OpenClaw** | openclaw-mesh (this repo) |\n| **diploid-agent** | [diploid-mesh](https://github.com/emiltsoi/diploid-mesh) |\n| **Shared registry** | [mesh-peer-registry](https://github.com/emiltsoi/mesh-peer-registry) / [PyPI](https://pypi.org/project/mesh-peer-registry/) |\n\nAll three share the same local vault layout (`mesh/agents/<name>/identity.yaml`) and the same optional `mesh-peer-registry` server. This means an OpenClaw agent can `mesh_send` to a Hermes peer, a Hermes agent can send to a diploid-agent peer, and an OpenClaw agent can receive a reply from a diploid agent — with the same identity files, the same envelope, and the same signatures everywhere.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}