libp2p-mesh 2026.5.13 → 2026.5.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +61 -23
  2. package/api.ts +1 -1
  3. package/dist/api.d.ts +1 -1
  4. package/dist/index.d.ts +0 -1
  5. package/dist/index.js +60 -24
  6. package/dist/runtime-setter-api.d.ts +4 -0
  7. package/dist/runtime-setter-api.js +19 -0
  8. package/dist/src/agent-tools.d.ts +63 -24
  9. package/dist/src/agent-tools.js +69 -34
  10. package/dist/src/channel.d.ts +1 -0
  11. package/dist/src/channel.js +20 -4
  12. package/dist/src/dht-registry.d.ts +38 -0
  13. package/dist/src/dht-registry.js +80 -0
  14. package/dist/src/inbound.d.ts +0 -3
  15. package/dist/src/inbound.js +29 -14
  16. package/dist/src/instance-id.d.ts +53 -0
  17. package/dist/src/instance-id.js +156 -0
  18. package/dist/src/mesh.js +310 -23
  19. package/dist/src/plugin.d.ts +1 -2
  20. package/dist/src/plugin.js +18 -30
  21. package/dist/src/types.d.ts +87 -0
  22. package/index.ts +60 -24
  23. package/openclaw.plugin.json +72 -33
  24. package/package.json +19 -7
  25. package/src/agent-tools.ts +69 -35
  26. package/src/channel.ts +25 -4
  27. package/src/dht-registry.ts +105 -0
  28. package/src/inbound.ts +35 -18
  29. package/src/instance-id.ts +221 -0
  30. package/src/mesh.ts +368 -27
  31. package/src/plugin.ts +25 -36
  32. package/src/types.ts +95 -0
  33. package/src/agent-tools-feishu.test.ts +0 -68
  34. package/src/config-schema.test.ts +0 -63
  35. package/src/feishu-channel.test.ts +0 -191
  36. package/src/feishu-channel.ts +0 -253
  37. package/src/feishu-client.test.ts +0 -303
  38. package/src/feishu-client.ts +0 -178
  39. package/src/feishu-e2e.test.ts +0 -90
  40. package/src/feishu-types.test.ts +0 -125
  41. package/src/feishu-types.ts +0 -51
  42. package/src/inbound-feishu.test.ts +0 -91
  43. package/src/index.ts +0 -1
  44. package/src/plugin-registration.test.ts +0 -60
package/README.md CHANGED
@@ -9,7 +9,7 @@ P2P mesh network plugin for OpenClaw. Enables direct peer-to-peer communication
9
9
  - **Broadcast** — Publish messages to a shared topic, flood-fill forwarded across the mesh
10
10
  - **Bootstrap Mode** — Optional static bootstrap peer list for non-LAN scenarios
11
11
  - **WebSocket Transport** — Optional WebSocket support for NAT/firewall-friendly connections
12
- - **Feishu Integration (Beta)** — Receive and send messages via Feishu (Lark) IM, with automatic P2P forwarding
12
+ - **NAT Traversal** — Built-in AutoNAT + UPnP + Circuit Relay v2 + DCUtR for peers behind home routers / firewalls
13
13
 
14
14
  ## Requirements
15
15
 
@@ -22,7 +22,7 @@ P2P mesh network plugin for OpenClaw. Enables direct peer-to-peer communication
22
22
  ### Method 1: Via OpenClaw CLI (Recommended)
23
23
 
24
24
  ```bash
25
- openclaw plugins install libp2p-mesh-v1
25
+ openclaw install openclaw-libp2p-mesh
26
26
  ```
27
27
 
28
28
  ### Method 2: Manual (npm)
@@ -31,7 +31,7 @@ openclaw plugins install libp2p-mesh-v1
31
31
 
32
32
  ```bash
33
33
  cd ~/.openclaw/npm
34
- npm install libp2p-mesh-v1
34
+ npm install openclaw-libp2p-mesh
35
35
  ```
36
36
 
37
37
  然后刷新插件注册表:
@@ -146,34 +146,72 @@ If peers are on different networks, use a bootstrap node:
146
146
  | `enableWebSocket` | `boolean` | `false` | Enable WebSocket transport for browser/NAT compatibility |
147
147
  | `meshTopic` | `string` | `"openclaw-mesh"` | Default broadcast topic |
148
148
  | `enableAgentSync` | `boolean` | `true` | Enable agent state synchronization over the mesh |
149
+ | `enableNATTraversal` | `boolean` | `true` | Master switch for identify + AutoNAT + UPnP + Circuit Relay v2 + DCUtR |
150
+ | `enableIdentify` | `boolean` | `true` | libp2p identify protocol (required by AutoNAT and DCUtR) |
151
+ | `enableAutoNAT` | `boolean` | `true` | AutoNAT — detect whether this node is publicly reachable |
152
+ | `enableUPnP` | `boolean` | `true` | Attempt UPnP/PMP port mapping on the local gateway |
153
+ | `enableCircuitRelay` | `boolean` | `true` | Dial peers via /p2p-circuit relay addresses |
154
+ | `enableCircuitRelayServer` | `boolean` | `false` | Act as a Circuit Relay v2 server (only enable on a public node) |
155
+ | `enableDCUtR` | `boolean` | `true` | Hole-punching: upgrade a relayed connection to a direct one |
156
+ | `relayList` | `string[]` | `[]` | Multiaddrs of relays to reserve a slot on |
157
+ | `discoverRelays` | `number` | `0` | Auto-discover this many relays via content routing |
158
+ | `announceAddrs` | `string[]` | `[]` | Extra multiaddrs to announce on top of auto-detected ones |
149
159
 
150
- ## Feishu 集成(Beta)
160
+ ## NAT Traversal
151
161
 
152
- OpenClaw 可以通过飞书与用户交互。配置后,你的 OpenClaw 实例会:
153
- - 接收你在飞书中的消息
154
- - 通过 P2P mesh 与其他 OpenClaw 实例通信
155
- - 将 P2P 消息自动转发到你的飞书
162
+ When both peers have a routable address (same LAN, public IPs, or working port-forwarding) no extra setup is needed. The defaults above kick in automatically:
156
163
 
157
- ### 配置步骤
164
+ - **UPnP** asks your home router to open a port for libp2p TCP.
165
+ - **AutoNAT** asks peers to verify whether you're reachable from the outside.
166
+ - If you're not directly reachable, **Circuit Relay v2** lets another peer (the "relay") forward traffic on your behalf. The relay only sees encrypted bytes — Noise still terminates end-to-end at the original peers.
167
+ - Once a relayed connection is established, **DCUtR** tries to upgrade it to a direct connection via simultaneous TCP open (hole punching). This works for most home NATs (full-cone, restricted-cone, port-restricted) but not symmetric NATs (CGNAT, some carrier networks).
158
168
 
159
- 1. 在[飞书开放平台](https://open.feishu.cn/)创建企业自建应用
160
- 2. 开启"机器人"能力,获取 App ID 和 App Secret
161
- 3. 配置事件订阅:请求地址填 `http://<your-host>:9222/webhook/feishu`
162
- 4. 订阅事件类型:`im.message.receive_v1`
163
- 5. 在 OpenClaw 配置中添加:
169
+ ### Behind a NAT — minimal config
164
170
 
165
- ```yaml
166
- feishu:
167
- appId: "cli-xxxxxxxxxx"
168
- appSecret: "xxxxxxxxxxxxxxxx"
169
- webhookPort: 9222
170
- webhookPath: "/webhook/feishu"
171
+ You need at least one relay node with a public IP. Set it in `relayList`:
172
+
173
+ ```json
174
+ {
175
+ "plugins": {
176
+ "libp2p-mesh": {
177
+ "enabled": true,
178
+ "config": {
179
+ "discovery": "bootstrap",
180
+ "bootstrapList": [
181
+ "/ip4/<RELAY-IP>/tcp/4001/p2p/<RELAY-PEER-ID>"
182
+ ],
183
+ "relayList": [
184
+ "/ip4/<RELAY-IP>/tcp/4001/p2p/<RELAY-PEER-ID>"
185
+ ]
186
+ }
187
+ }
188
+ }
189
+ }
171
190
  ```
172
191
 
173
- ### 部署要求
192
+ After start-up you should see your node listening on a `/p2p-circuit` address — that's how remote peers will reach you.
193
+
194
+ ### Running your own relay on a public VM
195
+
196
+ Add `enableCircuitRelayServer: true` to your config and announce the public address so other peers can dial you:
197
+
198
+ ```json
199
+ {
200
+ "plugins": {
201
+ "libp2p-mesh": {
202
+ "enabled": true,
203
+ "config": {
204
+ "discovery": "bootstrap",
205
+ "listenAddrs": ["/ip4/0.0.0.0/tcp/4001"],
206
+ "announceAddrs": ["/ip4/<PUBLIC-IP>/tcp/4001"],
207
+ "enableCircuitRelayServer": true
208
+ }
209
+ }
210
+ }
211
+ }
212
+ ```
174
213
 
175
- - webhook 端口必须对飞书服务器可达(公网 IP 或端口映射)
176
- - 本地开发可以使用 ngrok: `ngrok http 9222`
214
+ > Detailed walkthrough including how to rent a cloud VM is in `../TESTING_NAT.md`.
177
215
 
178
216
  ## Usage: Two Computers on the Same LAN
179
217
 
package/api.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { createMeshNetwork } from "./src/mesh.js";
2
- export type { MeshNetwork, P2PMessage, MeshConfig } from "./src/types.js";
2
+ export type { MeshNetwork, P2PMessage, MeshConfig, InstanceIdentity } from "./src/types.js";
package/dist/api.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { createMeshNetwork } from "./src/mesh.js";
2
- export type { MeshNetwork, P2PMessage, MeshConfig } from "./src/types.js";
2
+ export type { MeshNetwork, P2PMessage, MeshConfig, InstanceIdentity } from "./src/types.js";
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type { OpenClawPluginConfigSchema } from "openclaw/plugin-sdk/core";
2
- export declare function createLibp2pMeshConfigSchema(): OpenClawPluginConfigSchema;
3
2
  declare const _default: {
4
3
  id: string;
5
4
  name: string;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { definePluginEntry } from "openclaw/plugin-sdk/core";
2
2
  import { registerLibp2pMesh } from "./src/plugin.js";
3
- export function createLibp2pMeshConfigSchema() {
3
+ function createLibp2pMeshConfigSchema() {
4
4
  return {
5
5
  safeParse(value) {
6
6
  if (value === undefined) {
@@ -36,6 +36,7 @@ export function createLibp2pMeshConfigSchema() {
36
36
  bootstrapList: {
37
37
  type: "array",
38
38
  items: { type: "string" },
39
+ description: "List of bootstrap multiaddrs for WAN discovery (required when discovery=dht or bootstrap)",
39
40
  },
40
41
  meshTopic: {
41
42
  type: "string",
@@ -49,29 +50,64 @@ export function createLibp2pMeshConfigSchema() {
49
50
  type: "boolean",
50
51
  default: true,
51
52
  },
52
- feishu: {
53
- type: "object",
54
- description: "Feishu integration configuration",
55
- properties: {
56
- appId: {
57
- type: "string",
58
- description: "Feishu app App ID",
59
- },
60
- appSecret: {
61
- type: "string",
62
- description: "Feishu app App Secret",
63
- },
64
- webhookPort: {
65
- type: "number",
66
- default: 9222,
67
- description: "Port for Feishu webhook callback (default: 9222)",
68
- },
69
- webhookPath: {
70
- type: "string",
71
- default: "/webhook/feishu",
72
- description: "Path for Feishu webhook callback",
73
- },
74
- },
53
+ enableDHT: {
54
+ type: "boolean",
55
+ default: true,
56
+ description: "Enable DHT for WAN peer discovery and pubkey registry. Default true when discovery=dht, can be explicitly disabled.",
57
+ },
58
+ instanceName: {
59
+ type: "string",
60
+ description: "Custom name for this OpenClaw instance (used in InstanceID). Defaults to \"<username>-<hostname>\".",
61
+ },
62
+ enableNATTraversal: {
63
+ type: "boolean",
64
+ default: true,
65
+ description: "Master switch for the NAT traversal stack (identify + AutoNAT + UPnP + Circuit Relay v2 + DCUtR). Set to false to restore pre-NAT behaviour.",
66
+ },
67
+ enableIdentify: {
68
+ type: "boolean",
69
+ default: true,
70
+ description: "Run the libp2p identify protocol; required by AutoNAT and DCUtR.",
71
+ },
72
+ enableAutoNAT: {
73
+ type: "boolean",
74
+ default: true,
75
+ description: "Use AutoNAT to learn whether this node is publicly reachable.",
76
+ },
77
+ enableUPnP: {
78
+ type: "boolean",
79
+ default: true,
80
+ description: "Attempt UPnP/PMP port mapping against the local gateway so other peers can dial us directly when behind a home router.",
81
+ },
82
+ enableCircuitRelay: {
83
+ type: "boolean",
84
+ default: true,
85
+ description: "Allow this node to dial peers via /p2p-circuit relay addresses and to reserve a slot on the relays in relayList.",
86
+ },
87
+ enableCircuitRelayServer: {
88
+ type: "boolean",
89
+ default: false,
90
+ description: "Act as a Circuit Relay v2 SERVER for other peers. Only enable on a publicly reachable node (e.g. a cloud VM).",
91
+ },
92
+ enableDCUtR: {
93
+ type: "boolean",
94
+ default: true,
95
+ description: "Direct Connection Upgrade through Relay (hole punching). Upgrades a relayed connection to a direct one when possible.",
96
+ },
97
+ relayList: {
98
+ type: "array",
99
+ items: { type: "string" },
100
+ description: "Multiaddrs of relay nodes to reserve a slot on (each entry must end in /p2p/<peer-id>).",
101
+ },
102
+ discoverRelays: {
103
+ type: "number",
104
+ default: 0,
105
+ description: "How many relays to auto-discover via content routing. Requires DHT. 0 disables discovery.",
106
+ },
107
+ announceAddrs: {
108
+ type: "array",
109
+ items: { type: "string" },
110
+ description: "Extra multiaddrs to announce to the network (useful when running behind a known port forward where AutoNAT cannot probe).",
75
111
  },
76
112
  },
77
113
  },
@@ -0,0 +1,4 @@
1
+ import type { MeshNetwork } from "./src/types.js";
2
+ export declare function setLibp2pMeshRuntime(mesh: MeshNetwork): void;
3
+ export declare function getLibp2pMeshRuntime(): MeshNetwork;
4
+ export declare function hasLibp2pMeshRuntime(): boolean;
@@ -0,0 +1,19 @@
1
+ // Narrow setter for the libp2p-mesh runtime. The bundled channel entry needs
2
+ // a static `libp2pMeshPlugin` export to satisfy openclaw's bundled-channel
3
+ // contract; the channel itself however needs the mesh runtime, which is
4
+ // constructed inside registerFull(). We bridge the two via this module-level
5
+ // holder: registerFull() calls setLibp2pMeshRuntime(mesh) after starting the
6
+ // service, and channel.ts reads from getLibp2pMeshRuntime() when sending.
7
+ let _runtime = null;
8
+ export function setLibp2pMeshRuntime(mesh) {
9
+ _runtime = mesh;
10
+ }
11
+ export function getLibp2pMeshRuntime() {
12
+ if (!_runtime) {
13
+ throw new Error("libp2p-mesh: runtime not initialized — registerFull() must call setLibp2pMeshRuntime() before any channel call");
14
+ }
15
+ return _runtime;
16
+ }
17
+ export function hasLibp2pMeshRuntime() {
18
+ return _runtime !== null;
19
+ }
@@ -1,5 +1,4 @@
1
1
  import type { MeshNetwork } from "./types.js";
2
- import type { FeishuApiClient } from "./feishu-client.js";
3
2
  export declare function buildP2PTools(mesh: MeshNetwork): ({
4
3
  name: string;
5
4
  label: string;
@@ -128,37 +127,27 @@ export declare function buildP2PTools(mesh: MeshNetwork): ({
128
127
  };
129
128
  isError: boolean;
130
129
  }>;
131
- })[];
132
- export declare function buildFeishuTools(feishuClient: FeishuApiClient | null): {
130
+ } | {
133
131
  name: string;
134
132
  label: string;
135
133
  description: string;
136
134
  parameters: {
137
135
  type: "object";
138
136
  properties: {
139
- openId: {
140
- type: "string";
141
- description: string;
142
- };
143
- message: {
144
- type: "string";
145
- description: string;
146
- };
137
+ peerId?: undefined;
138
+ message?: undefined;
139
+ topic?: undefined;
147
140
  };
148
- required: string[];
141
+ required?: undefined;
149
142
  };
150
- execute(_toolCallId: string, params: {
151
- openId: string;
152
- message: string;
153
- }): Promise<{
143
+ execute(_toolCallId: string): Promise<{
154
144
  content: {
155
145
  type: "text";
156
146
  text: string;
157
147
  }[];
158
148
  details: {
159
- sent: boolean;
160
- openId: string;
161
- messageId: string | undefined;
149
+ initialized: boolean;
150
+ identity?: undefined;
162
151
  error?: undefined;
163
152
  };
164
153
  isError?: undefined;
@@ -168,11 +157,61 @@ export declare function buildFeishuTools(feishuClient: FeishuApiClient | null):
168
157
  text: string;
169
158
  }[];
170
159
  details: {
171
- sent: boolean;
172
- openId: string;
173
- error: string | undefined;
174
- messageId?: undefined;
160
+ identity: import("./types.js").InstanceIdentity;
161
+ initialized?: undefined;
162
+ error?: undefined;
163
+ };
164
+ isError?: undefined;
165
+ } | {
166
+ content: {
167
+ type: "text";
168
+ text: string;
169
+ }[];
170
+ details: {
171
+ error: string;
172
+ initialized?: undefined;
173
+ identity?: undefined;
175
174
  };
176
175
  isError: boolean;
177
176
  }>;
178
- }[];
177
+ } | {
178
+ name: string;
179
+ label: string;
180
+ description: string;
181
+ parameters: {
182
+ type: "object";
183
+ properties: {
184
+ peerId?: undefined;
185
+ message?: undefined;
186
+ topic?: undefined;
187
+ };
188
+ required?: undefined;
189
+ };
190
+ execute(_toolCallId: string): Promise<{
191
+ content: {
192
+ type: "text";
193
+ text: string;
194
+ }[];
195
+ details: {
196
+ peerId: string;
197
+ instanceId: string | undefined;
198
+ listenAddrs: string[];
199
+ connectedPeers: string[];
200
+ error?: undefined;
201
+ };
202
+ isError?: undefined;
203
+ } | {
204
+ content: {
205
+ type: "text";
206
+ text: string;
207
+ }[];
208
+ details: {
209
+ error: string;
210
+ peerId?: undefined;
211
+ instanceId?: undefined;
212
+ listenAddrs?: undefined;
213
+ connectedPeers?: undefined;
214
+ };
215
+ isError: boolean;
216
+ }>;
217
+ })[];
@@ -112,48 +112,83 @@ export function buildP2PTools(mesh) {
112
112
  }
113
113
  },
114
114
  },
115
- ];
116
- }
117
- export function buildFeishuTools(feishuClient) {
118
- if (!feishuClient)
119
- return [];
120
- return [
121
115
  {
122
- name: "feishu_send_message",
123
- label: "Feishu Send Message",
124
- description: "Send a text message to a Feishu user by their openId. Use this to notify users through Feishu.",
116
+ name: "p2p_get_instance_identity",
117
+ label: "P2P Get Instance Identity",
118
+ description: "Get the OpenClaw instance identity (lightweight BAID-inspired ID) of this node.",
125
119
  parameters: {
126
120
  type: "object",
127
- properties: {
128
- openId: {
129
- type: "string",
130
- description: "Feishu openId of the recipient",
131
- },
132
- message: {
133
- type: "string",
134
- description: "Text message content to send",
135
- },
136
- },
137
- required: ["openId", "message"],
121
+ properties: {},
138
122
  },
139
- async execute(_toolCallId, params) {
140
- const result = await feishuClient.sendMessage(params.openId, params.message);
141
- if (result.success) {
123
+ async execute(_toolCallId) {
124
+ try {
125
+ const identity = mesh.getInstanceIdentity();
126
+ if (!identity) {
127
+ return {
128
+ content: [{ type: "text", text: "Instance identity not yet initialized." }],
129
+ details: { initialized: false },
130
+ };
131
+ }
132
+ const lines = [
133
+ `Instance ID: ${identity.id}`,
134
+ `Name: ${identity.name}`,
135
+ `Pubkey: ${identity.pubkey.slice(0, 32)}...`,
136
+ `Binding: ${identity.binding.slice(0, 16)}...`,
137
+ `Bound to: ${identity.bindingComponents.username}@${identity.bindingComponents.hostname} (${identity.bindingComponents.platform})`,
138
+ `Created: ${new Date(identity.createdAt).toLocaleString()}`,
139
+ ];
140
+ return {
141
+ content: [{ type: "text", text: lines.join("\n") }],
142
+ details: { identity },
143
+ };
144
+ }
145
+ catch (err) {
142
146
  return {
143
- content: [{ type: "text", text: `Message sent to Feishu user ${params.openId}` }],
144
- details: { sent: true, openId: params.openId, messageId: result.messageId },
147
+ content: [{ type: "text", text: `Error: ${String(err)}` }],
148
+ details: { error: String(err) },
149
+ isError: true,
145
150
  };
146
151
  }
147
- return {
148
- content: [
149
- {
150
- type: "text",
151
- text: `Failed to send Feishu message to ${params.openId}: ${result.error}`,
152
+ },
153
+ },
154
+ {
155
+ name: "p2p_get_network_info",
156
+ label: "P2P Get Network Info",
157
+ description: "Get combined network and identity info: Peer ID, Instance ID, listen addresses, and connected peers.",
158
+ parameters: {
159
+ type: "object",
160
+ properties: {},
161
+ },
162
+ async execute(_toolCallId) {
163
+ try {
164
+ const identity = mesh.getInstanceIdentity();
165
+ const peerId = mesh.getLocalPeerId();
166
+ const addrs = mesh.getMultiaddrs();
167
+ const peers = mesh.getConnectedPeers();
168
+ const lines = [
169
+ `Peer ID: ${peerId || "(not started)"}`,
170
+ `Instance ID: ${identity?.id || "(not initialized)"}`,
171
+ `Instance: ${identity?.bindingComponents.username}@${identity?.bindingComponents.hostname}` || "",
172
+ `Listen Addrs: ${addrs.length > 0 ? addrs.join(", ") : "(none)"}`,
173
+ `Connected: ${peers.length} peer(s)${peers.length > 0 ? ": " + peers.join(", ") : ""}`,
174
+ ];
175
+ return {
176
+ content: [{ type: "text", text: lines.join("\n") }],
177
+ details: {
178
+ peerId,
179
+ instanceId: identity?.id,
180
+ listenAddrs: addrs,
181
+ connectedPeers: peers,
152
182
  },
153
- ],
154
- details: { sent: false, openId: params.openId, error: result.error },
155
- isError: true,
156
- };
183
+ };
184
+ }
185
+ catch (err) {
186
+ return {
187
+ content: [{ type: "text", text: `Error: ${String(err)}` }],
188
+ details: { error: String(err) },
189
+ isError: true,
190
+ };
191
+ }
157
192
  },
158
193
  },
159
194
  ];
@@ -1,3 +1,4 @@
1
1
  import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
2
2
  import type { MeshNetwork } from "./types.js";
3
+ export declare const libp2pMeshPlugin: ChannelPlugin;
3
4
  export declare function createLibp2pMeshChannel(mesh: MeshNetwork): ChannelPlugin;
@@ -1,6 +1,7 @@
1
1
  import { createChatChannelPlugin } from "openclaw/plugin-sdk/core";
2
2
  import { sendViaMesh } from "./send.js";
3
- export function createLibp2pMeshChannel(mesh) {
3
+ import { getLibp2pMeshRuntime, hasLibp2pMeshRuntime, } from "../runtime-setter-api.js";
4
+ function buildChannel(getMesh) {
4
5
  return createChatChannelPlugin({
5
6
  base: {
6
7
  id: "libp2p-mesh",
@@ -39,7 +40,9 @@ export function createLibp2pMeshChannel(mesh) {
39
40
  name: "default",
40
41
  configured: true,
41
42
  enabled: true,
42
- connected: mesh.getConnectedPeers().length > 0,
43
+ connected: hasLibp2pMeshRuntime()
44
+ ? getMesh().getConnectedPeers().length > 0
45
+ : false,
43
46
  }),
44
47
  },
45
48
  messaging: {
@@ -54,13 +57,26 @@ export function createLibp2pMeshChannel(mesh) {
54
57
  deliveryMode: "gateway",
55
58
  sendText: async ({ to, text }) => {
56
59
  try {
57
- await sendViaMesh(mesh, to, text);
60
+ await sendViaMesh(getMesh(), to, text);
58
61
  return { channel: "libp2p-mesh", messageId: `p2p-${Date.now()}` };
59
62
  }
60
63
  catch (err) {
61
- return { channel: "libp2p-mesh", messageId: `p2p-${Date.now()}`, meta: { error: String(err) } };
64
+ return {
65
+ channel: "libp2p-mesh",
66
+ messageId: `p2p-${Date.now()}`,
67
+ meta: { error: String(err) },
68
+ };
62
69
  }
63
70
  },
64
71
  },
65
72
  });
66
73
  }
74
+ // Static channel plugin export for the bundled-channel-entry contract.
75
+ // The mesh instance is resolved lazily through runtime-setter-api.ts, which
76
+ // plugin.ts populates after starting the mesh service.
77
+ export const libp2pMeshPlugin = buildChannel(getLibp2pMeshRuntime);
78
+ // Backwards-compatible factory: kept so any caller that still passes the mesh
79
+ // instance directly (e.g. the standalone plugin entry) continues to work.
80
+ export function createLibp2pMeshChannel(mesh) {
81
+ return buildChannel(() => mesh);
82
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * DHT-based public key registry for cross-instance identity verification.
3
+ *
4
+ * Each OpenClaw instance publishes its Ed25519 pubkey to the DHT under the key:
5
+ * openclaw:pubkey:<instanceId>
6
+ *
7
+ * Other instances can look up this pubkey to verify message signatures.
8
+ */
9
+ import type { KadDHT } from "@libp2p/kad-dht";
10
+ /**
11
+ * Register this instance's pubkey in the DHT.
12
+ * Other nodes can later look it up to verify signatures from this instance.
13
+ */
14
+ export declare function registerPubkey(dht: KadDHT, instanceId: string, pubkey: string, logger?: {
15
+ info?: (msg: string) => void;
16
+ debug?: (msg: string) => void;
17
+ warn?: (msg: string) => void;
18
+ }): Promise<void>;
19
+ /**
20
+ * Look up a pubkey from the DHT for the given instanceId.
21
+ * Results are cached locally to avoid repeated DHT queries.
22
+ */
23
+ export declare function lookupPubkey(dht: KadDHT, instanceId: string, logger?: {
24
+ info?: (msg: string) => void;
25
+ debug?: (msg: string) => void;
26
+ warn?: (msg: string) => void;
27
+ }): Promise<string | undefined>;
28
+ /**
29
+ * Clear the local pubkey cache.
30
+ */
31
+ export declare function clearPubkeyCache(): void;
32
+ /**
33
+ * Get cache stats for observability.
34
+ */
35
+ export declare function getCacheStats(): {
36
+ size: number;
37
+ keys: string[];
38
+ };