libp2p-mesh 2026.5.13 → 2026.5.15
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.
- package/README.md +61 -23
- package/api.ts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +60 -24
- package/dist/runtime-setter-api.d.ts +4 -0
- package/dist/runtime-setter-api.js +19 -0
- package/dist/src/agent-tools.d.ts +63 -24
- package/dist/src/agent-tools.js +69 -34
- package/dist/src/channel.d.ts +1 -0
- package/dist/src/channel.js +20 -4
- package/dist/src/dht-registry.d.ts +38 -0
- package/dist/src/dht-registry.js +80 -0
- package/dist/src/inbound.d.ts +0 -3
- package/dist/src/inbound.js +29 -14
- package/dist/src/instance-id.d.ts +53 -0
- package/dist/src/instance-id.js +156 -0
- package/dist/src/mesh.js +310 -23
- package/dist/src/plugin.d.ts +1 -2
- package/dist/src/plugin.js +18 -30
- package/dist/src/types.d.ts +87 -0
- package/index.ts +60 -24
- package/openclaw.plugin.json +72 -33
- package/package.json +20 -8
- package/src/agent-tools.ts +69 -35
- package/src/channel.ts +25 -4
- package/src/dht-registry.ts +105 -0
- package/src/inbound.ts +35 -18
- package/src/instance-id.ts +221 -0
- package/src/mesh.ts +368 -27
- package/src/plugin.ts +25 -36
- package/src/types.ts +95 -0
- package/dist/src/agent-tools-feishu.test.d.ts +0 -1
- package/dist/src/agent-tools-feishu.test.js +0 -57
- package/dist/src/config-schema.test.d.ts +0 -1
- package/dist/src/config-schema.test.js +0 -55
- package/dist/src/feishu-channel.d.ts +0 -19
- package/dist/src/feishu-channel.js +0 -202
- package/dist/src/feishu-channel.test.d.ts +0 -1
- package/dist/src/feishu-channel.test.js +0 -166
- package/dist/src/feishu-client.d.ts +0 -27
- package/dist/src/feishu-client.js +0 -141
- package/dist/src/feishu-client.test.d.ts +0 -1
- package/dist/src/feishu-client.test.js +0 -271
- package/dist/src/feishu-e2e.test.d.ts +0 -1
- package/dist/src/feishu-e2e.test.js +0 -69
- package/dist/src/feishu-types.d.ts +0 -53
- package/dist/src/feishu-types.js +0 -1
- package/dist/src/feishu-types.test.d.ts +0 -1
- package/dist/src/feishu-types.test.js +0 -108
- package/dist/src/inbound-feishu.test.d.ts +0 -1
- package/dist/src/inbound-feishu.test.js +0 -70
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.js +0 -1
- package/dist/src/plugin-registration.test.d.ts +0 -1
- package/dist/src/plugin-registration.test.js +0 -42
- package/src/agent-tools-feishu.test.ts +0 -68
- package/src/config-schema.test.ts +0 -63
- package/src/feishu-channel.test.ts +0 -191
- package/src/feishu-channel.ts +0 -253
- package/src/feishu-client.test.ts +0 -303
- package/src/feishu-client.ts +0 -178
- package/src/feishu-e2e.test.ts +0 -90
- package/src/feishu-types.test.ts +0 -125
- package/src/feishu-types.ts +0 -51
- package/src/inbound-feishu.test.ts +0 -91
- package/src/index.ts +0 -1
- 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
|
-
- **
|
|
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
|
|
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
|
|
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
|
-
##
|
|
160
|
+
## NAT Traversal
|
|
151
161
|
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
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
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
|
-
|
|
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
|
-
|
|
53
|
-
type: "
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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,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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
};
|
|
143
|
-
message: {
|
|
144
|
-
type: "string";
|
|
145
|
-
description: string;
|
|
146
|
-
};
|
|
137
|
+
peerId?: undefined;
|
|
138
|
+
message?: undefined;
|
|
139
|
+
topic?: undefined;
|
|
147
140
|
};
|
|
148
|
-
required
|
|
141
|
+
required?: undefined;
|
|
149
142
|
};
|
|
150
|
-
execute(_toolCallId: string
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
error
|
|
174
|
-
|
|
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
|
+
})[];
|
package/dist/src/agent-tools.js
CHANGED
|
@@ -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: "
|
|
123
|
-
label: "
|
|
124
|
-
description: "
|
|
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
|
|
140
|
-
|
|
141
|
-
|
|
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: `
|
|
144
|
-
details: {
|
|
147
|
+
content: [{ type: "text", text: `Error: ${String(err)}` }],
|
|
148
|
+
details: { error: String(err) },
|
|
149
|
+
isError: true,
|
|
145
150
|
};
|
|
146
151
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
];
|
package/dist/src/channel.d.ts
CHANGED
package/dist/src/channel.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createChatChannelPlugin } from "openclaw/plugin-sdk/core";
|
|
2
2
|
import { sendViaMesh } from "./send.js";
|
|
3
|
-
|
|
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:
|
|
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(
|
|
60
|
+
await sendViaMesh(getMesh(), to, text);
|
|
58
61
|
return { channel: "libp2p-mesh", messageId: `p2p-${Date.now()}` };
|
|
59
62
|
}
|
|
60
63
|
catch (err) {
|
|
61
|
-
return {
|
|
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
|
+
};
|