libp2p-mesh 2026.5.16 → 2026.5.18
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 +75 -1
- package/dist/src/agent-tools.d.ts +110 -7
- package/dist/src/agent-tools.js +123 -11
- package/dist/src/identity-exchange.d.ts +13 -0
- package/dist/src/identity-exchange.js +36 -0
- package/dist/src/inbound.d.ts +10 -3
- package/dist/src/inbound.js +71 -2
- package/dist/src/mesh.js +2 -2
- package/dist/src/peer-identity.d.ts +23 -0
- package/dist/src/peer-identity.js +47 -0
- package/dist/src/plugin.js +49 -1
- package/dist/src/types.d.ts +5 -1
- package/openclaw.plugin.json +13 -1
- package/package.json +4 -2
- package/src/agent-tools.ts +123 -12
- package/src/identity-exchange.ts +67 -0
- package/src/inbound.ts +80 -5
- package/src/mesh.ts +2 -2
- package/src/peer-identity.ts +77 -0
- package/src/plugin.ts +65 -3
- package/src/types.ts +5 -1
package/README.md
CHANGED
|
@@ -42,7 +42,9 @@ openclaw plugins registry --refresh
|
|
|
42
42
|
|
|
43
43
|
The published npm package includes compiled JavaScript under `dist/`, so OpenClaw and acpx can load it directly.
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
After installing the plugin, you must:
|
|
46
|
+
|
|
47
|
+
1. **Enable the plugin** in `~/.openclaw/openclaw.json`:
|
|
46
48
|
|
|
47
49
|
```json
|
|
48
50
|
{
|
|
@@ -62,6 +64,78 @@ Then add to your `~/.openclaw/openclaw.json`:
|
|
|
62
64
|
}
|
|
63
65
|
```
|
|
64
66
|
|
|
67
|
+
2. **Set `tools.profile` to `full`** — OpenClaw defaults to `"coding"` profile, which **filters out** P2P tools. Even if you add them to `tools.allow`, the coding profile will still block them. You must change to `"full"` first:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"tools": {
|
|
72
|
+
"profile": "full",
|
|
73
|
+
"allow": [
|
|
74
|
+
"p2p_list_peers",
|
|
75
|
+
"p2p_send_message",
|
|
76
|
+
"p2p_broadcast"
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
> **Why `profile: "full"`?** The `"coding"` profile hardcodes a deny list that includes `p2p_list_peers`, `p2p_send_message`, and `p2p_broadcast`. With `profile: "coding"`, adding tools to `tools.allow` has no effect for these three — the profile filter runs first and removes them.
|
|
83
|
+
|
|
84
|
+
3. **Restart the gateway:**
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
openclaw gateway restart
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
4. **Verify tool registration:**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
openclaw plugins inspect libp2p-mesh --runtime --json | jq '.plugin.toolNames, .tools'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Expected output:
|
|
97
|
+
```json
|
|
98
|
+
[
|
|
99
|
+
"p2p_send_message",
|
|
100
|
+
"p2p_broadcast",
|
|
101
|
+
"p2p_list_peers",
|
|
102
|
+
"p2p_get_instance_identity",
|
|
103
|
+
"p2p_get_network_info"
|
|
104
|
+
]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### One-command setup (optional)
|
|
108
|
+
|
|
109
|
+
Instead of manually editing `openclaw.json`, run:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npx openclaw-libp2p-mesh-configure-tools
|
|
113
|
+
# or, from the plugin directory:
|
|
114
|
+
npm run configure-tools
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This patches `~/.openclaw/openclaw.json` with the correct `tools.profile` and `tools.allow` values. Use `--check` to preview without modifying:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm run configure-tools:check
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Troubleshooting
|
|
124
|
+
|
|
125
|
+
### "plugin must declare contracts.tools before registering agent tools"
|
|
126
|
+
|
|
127
|
+
Make sure you are running **OpenClaw >= 2026.6.5** and the plugin is installed via `openclaw install` or `npm install` into `~/.openclaw/npm`. The manifest (`openclaw.plugin.json`) ships with `contracts.tools` pre-declared, so no manual manifest edits are needed.
|
|
128
|
+
|
|
129
|
+
### Tools still not available after setup
|
|
130
|
+
|
|
131
|
+
1. Confirm `tools.profile` is `"full"` (not `"coding"`):
|
|
132
|
+
```bash
|
|
133
|
+
cat ~/.openclaw/openclaw.json | jq '.tools'
|
|
134
|
+
```
|
|
135
|
+
2. Confirm all three P2P tools are in `tools.allow`.
|
|
136
|
+
3. Restart the gateway (not just reload).
|
|
137
|
+
4. Check `openclaw plugins inspect libp2p-mesh --runtime --json` for diagnostics.
|
|
138
|
+
|
|
65
139
|
## Configuration
|
|
66
140
|
|
|
67
141
|
Add a `libp2p-mesh` block to your `openclaw.json` under `plugins`:
|
|
@@ -14,6 +14,7 @@ export declare function buildP2PTools(mesh: MeshNetwork): ({
|
|
|
14
14
|
type: "string";
|
|
15
15
|
description: string;
|
|
16
16
|
};
|
|
17
|
+
instanceId?: undefined;
|
|
17
18
|
topic?: undefined;
|
|
18
19
|
};
|
|
19
20
|
required: string[];
|
|
@@ -44,6 +45,78 @@ export declare function buildP2PTools(mesh: MeshNetwork): ({
|
|
|
44
45
|
};
|
|
45
46
|
isError: boolean;
|
|
46
47
|
}>;
|
|
48
|
+
} | {
|
|
49
|
+
name: string;
|
|
50
|
+
label: string;
|
|
51
|
+
description: string;
|
|
52
|
+
parameters: {
|
|
53
|
+
type: "object";
|
|
54
|
+
properties: {
|
|
55
|
+
instanceId: {
|
|
56
|
+
type: "string";
|
|
57
|
+
description: string;
|
|
58
|
+
};
|
|
59
|
+
message: {
|
|
60
|
+
type: "string";
|
|
61
|
+
description: string;
|
|
62
|
+
};
|
|
63
|
+
peerId?: undefined;
|
|
64
|
+
topic?: undefined;
|
|
65
|
+
};
|
|
66
|
+
required: string[];
|
|
67
|
+
};
|
|
68
|
+
execute(_toolCallId: string, params: {
|
|
69
|
+
instanceId: string;
|
|
70
|
+
message: string;
|
|
71
|
+
}, _ctx: any): Promise<{
|
|
72
|
+
content: {
|
|
73
|
+
type: "text";
|
|
74
|
+
text: string;
|
|
75
|
+
}[];
|
|
76
|
+
details: {
|
|
77
|
+
sent: boolean;
|
|
78
|
+
error: string;
|
|
79
|
+
instanceId?: undefined;
|
|
80
|
+
peerId?: undefined;
|
|
81
|
+
};
|
|
82
|
+
isError: boolean;
|
|
83
|
+
} | {
|
|
84
|
+
content: {
|
|
85
|
+
type: "text";
|
|
86
|
+
text: string;
|
|
87
|
+
}[];
|
|
88
|
+
details: {
|
|
89
|
+
sent: boolean;
|
|
90
|
+
instanceId: string;
|
|
91
|
+
error?: undefined;
|
|
92
|
+
peerId?: undefined;
|
|
93
|
+
};
|
|
94
|
+
isError: boolean;
|
|
95
|
+
} | {
|
|
96
|
+
content: {
|
|
97
|
+
type: "text";
|
|
98
|
+
text: string;
|
|
99
|
+
}[];
|
|
100
|
+
details: {
|
|
101
|
+
sent: boolean;
|
|
102
|
+
instanceId: string;
|
|
103
|
+
peerId: any;
|
|
104
|
+
error?: undefined;
|
|
105
|
+
};
|
|
106
|
+
isError?: undefined;
|
|
107
|
+
} | {
|
|
108
|
+
content: {
|
|
109
|
+
type: "text";
|
|
110
|
+
text: string;
|
|
111
|
+
}[];
|
|
112
|
+
details: {
|
|
113
|
+
sent: boolean;
|
|
114
|
+
instanceId: string;
|
|
115
|
+
error: string;
|
|
116
|
+
peerId?: undefined;
|
|
117
|
+
};
|
|
118
|
+
isError: boolean;
|
|
119
|
+
}>;
|
|
47
120
|
} | {
|
|
48
121
|
name: string;
|
|
49
122
|
label: string;
|
|
@@ -60,6 +133,7 @@ export declare function buildP2PTools(mesh: MeshNetwork): ({
|
|
|
60
133
|
description: string;
|
|
61
134
|
};
|
|
62
135
|
peerId?: undefined;
|
|
136
|
+
instanceId?: undefined;
|
|
63
137
|
};
|
|
64
138
|
required: string[];
|
|
65
139
|
};
|
|
@@ -98,19 +172,21 @@ export declare function buildP2PTools(mesh: MeshNetwork): ({
|
|
|
98
172
|
properties: {
|
|
99
173
|
peerId?: undefined;
|
|
100
174
|
message?: undefined;
|
|
175
|
+
instanceId?: undefined;
|
|
101
176
|
topic?: undefined;
|
|
102
177
|
};
|
|
103
178
|
required?: undefined;
|
|
104
179
|
};
|
|
105
|
-
execute(_toolCallId: string): Promise<{
|
|
180
|
+
execute(_toolCallId: string, _params: any, ctx: any): Promise<{
|
|
106
181
|
content: {
|
|
107
182
|
type: "text";
|
|
108
183
|
text: string;
|
|
109
184
|
}[];
|
|
110
185
|
details: {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
186
|
+
peers: {
|
|
187
|
+
instanceId: any;
|
|
188
|
+
channel: any;
|
|
189
|
+
}[];
|
|
114
190
|
error?: undefined;
|
|
115
191
|
};
|
|
116
192
|
isError?: undefined;
|
|
@@ -121,9 +197,7 @@ export declare function buildP2PTools(mesh: MeshNetwork): ({
|
|
|
121
197
|
}[];
|
|
122
198
|
details: {
|
|
123
199
|
error: string;
|
|
124
|
-
|
|
125
|
-
connectedPeers?: undefined;
|
|
126
|
-
count?: undefined;
|
|
200
|
+
peers?: undefined;
|
|
127
201
|
};
|
|
128
202
|
isError: boolean;
|
|
129
203
|
}>;
|
|
@@ -136,6 +210,7 @@ export declare function buildP2PTools(mesh: MeshNetwork): ({
|
|
|
136
210
|
properties: {
|
|
137
211
|
peerId?: undefined;
|
|
138
212
|
message?: undefined;
|
|
213
|
+
instanceId?: undefined;
|
|
139
214
|
topic?: undefined;
|
|
140
215
|
};
|
|
141
216
|
required?: undefined;
|
|
@@ -183,6 +258,7 @@ export declare function buildP2PTools(mesh: MeshNetwork): ({
|
|
|
183
258
|
properties: {
|
|
184
259
|
peerId?: undefined;
|
|
185
260
|
message?: undefined;
|
|
261
|
+
instanceId?: undefined;
|
|
186
262
|
topic?: undefined;
|
|
187
263
|
};
|
|
188
264
|
required?: undefined;
|
|
@@ -214,4 +290,31 @@ export declare function buildP2PTools(mesh: MeshNetwork): ({
|
|
|
214
290
|
};
|
|
215
291
|
isError: boolean;
|
|
216
292
|
}>;
|
|
293
|
+
} | {
|
|
294
|
+
name: string;
|
|
295
|
+
label: string;
|
|
296
|
+
description: string;
|
|
297
|
+
parameters: {
|
|
298
|
+
type: "object";
|
|
299
|
+
properties: {
|
|
300
|
+
peerId?: undefined;
|
|
301
|
+
message?: undefined;
|
|
302
|
+
instanceId?: undefined;
|
|
303
|
+
topic?: undefined;
|
|
304
|
+
};
|
|
305
|
+
required?: undefined;
|
|
306
|
+
};
|
|
307
|
+
execute(_toolCallId: string, _params: {}, _ctx: any): Promise<{
|
|
308
|
+
content: {
|
|
309
|
+
type: "text";
|
|
310
|
+
text: string;
|
|
311
|
+
}[];
|
|
312
|
+
details: {
|
|
313
|
+
knownIdentities: ({
|
|
314
|
+
instanceId: any;
|
|
315
|
+
agentId: any;
|
|
316
|
+
channel: any;
|
|
317
|
+
} | null)[];
|
|
318
|
+
};
|
|
319
|
+
}>;
|
|
217
320
|
})[];
|
package/dist/src/agent-tools.js
CHANGED
|
@@ -3,7 +3,7 @@ export function buildP2PTools(mesh) {
|
|
|
3
3
|
{
|
|
4
4
|
name: "p2p_send_message",
|
|
5
5
|
label: "P2P Send Message",
|
|
6
|
-
description: "Send a direct message to another agent via the P2P mesh network.",
|
|
6
|
+
description: "Send a direct message to another agent via the P2P mesh network by Peer ID.",
|
|
7
7
|
parameters: {
|
|
8
8
|
type: "object",
|
|
9
9
|
properties: {
|
|
@@ -40,6 +40,72 @@ export function buildP2PTools(mesh) {
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
},
|
|
43
|
+
{
|
|
44
|
+
name: "p2p_send_to_instance",
|
|
45
|
+
label: "P2P Send to Instance",
|
|
46
|
+
description: "Send a direct message to a peer by its human-readable Instance ID (e.g. alice-mac).",
|
|
47
|
+
parameters: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
instanceId: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description: "Target Instance ID (e.g. alice-mac@AQIDBAU.7a3f9e2b)",
|
|
53
|
+
},
|
|
54
|
+
message: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "Message content to send",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ["instanceId", "message"],
|
|
60
|
+
},
|
|
61
|
+
async execute(_toolCallId, params, _ctx) {
|
|
62
|
+
const peerIdentityMap = _ctx?.peerIdentityMap;
|
|
63
|
+
if (!peerIdentityMap) {
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: "text", text: "Internal error: peer identity map not available." }],
|
|
66
|
+
details: { sent: false, error: "peerIdentityMap missing" },
|
|
67
|
+
isError: true,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const lookup = peerIdentityMap.resolveByInstanceId(params.instanceId);
|
|
71
|
+
if (!lookup) {
|
|
72
|
+
return {
|
|
73
|
+
content: [
|
|
74
|
+
{
|
|
75
|
+
type: "text",
|
|
76
|
+
text: `Unknown Instance ID: "${params.instanceId}". Use p2p_list_peers to see available users.`,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
details: { sent: false, instanceId: params.instanceId },
|
|
80
|
+
isError: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
await mesh.sendToPeer(lookup.peerId, params.message);
|
|
85
|
+
return {
|
|
86
|
+
content: [
|
|
87
|
+
{
|
|
88
|
+
type: "text",
|
|
89
|
+
text: `Message sent to ${params.instanceId}`,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
details: { sent: true, instanceId: params.instanceId, peerId: lookup.peerId },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
return {
|
|
97
|
+
content: [
|
|
98
|
+
{
|
|
99
|
+
type: "text",
|
|
100
|
+
text: `Failed to send message to ${params.instanceId}: ${String(err)}`,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
details: { sent: false, instanceId: params.instanceId, error: String(err) },
|
|
104
|
+
isError: true,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
},
|
|
43
109
|
{
|
|
44
110
|
name: "p2p_broadcast",
|
|
45
111
|
label: "P2P Broadcast",
|
|
@@ -83,24 +149,42 @@ export function buildP2PTools(mesh) {
|
|
|
83
149
|
{
|
|
84
150
|
name: "p2p_list_peers",
|
|
85
151
|
label: "P2P List Peers",
|
|
86
|
-
description: "List currently connected peers
|
|
152
|
+
description: "List currently connected peers with their Instance IDs and channels.",
|
|
87
153
|
parameters: {
|
|
88
154
|
type: "object",
|
|
89
155
|
properties: {},
|
|
90
156
|
},
|
|
91
|
-
async execute(_toolCallId) {
|
|
157
|
+
async execute(_toolCallId, _params, ctx) {
|
|
92
158
|
try {
|
|
159
|
+
const peerIdentityMap = ctx?.peerIdentityMap;
|
|
93
160
|
const peers = mesh.getConnectedPeers();
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
161
|
+
if (peers.length === 0) {
|
|
162
|
+
return {
|
|
163
|
+
content: [{ type: "text", text: "No peers currently connected." }],
|
|
164
|
+
details: { peers: [] },
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
const userList = peers
|
|
168
|
+
.map((p) => {
|
|
169
|
+
const identity = peerIdentityMap?.resolve(p);
|
|
170
|
+
if (identity?.instanceId) {
|
|
171
|
+
const ch = identity.channel ? ` (${identity.channel})` : "";
|
|
172
|
+
return ` - ${identity.instanceId}${ch}`;
|
|
173
|
+
}
|
|
174
|
+
return ` - ${p.slice(0, 12)}... (identity unknown)`;
|
|
175
|
+
})
|
|
176
|
+
.join("\n");
|
|
177
|
+
const text = `在线用户 (${peers.length}):\n${userList}`;
|
|
178
|
+
const safePeers = peers.map((p) => {
|
|
179
|
+
const identity = peerIdentityMap?.resolve(p);
|
|
180
|
+
return {
|
|
181
|
+
instanceId: identity?.instanceId || p.slice(0, 12) + "...",
|
|
182
|
+
channel: identity?.channel || "unknown",
|
|
183
|
+
};
|
|
184
|
+
});
|
|
97
185
|
return {
|
|
98
186
|
content: [{ type: "text", text }],
|
|
99
|
-
details: {
|
|
100
|
-
localPeerId: mesh.getLocalPeerId(),
|
|
101
|
-
connectedPeers: peers,
|
|
102
|
-
count: peers.length,
|
|
103
|
-
},
|
|
187
|
+
details: { peers: safePeers },
|
|
104
188
|
};
|
|
105
189
|
}
|
|
106
190
|
catch (err) {
|
|
@@ -191,5 +275,33 @@ export function buildP2PTools(mesh) {
|
|
|
191
275
|
}
|
|
192
276
|
},
|
|
193
277
|
},
|
|
278
|
+
{
|
|
279
|
+
name: "p2p_relay_status",
|
|
280
|
+
label: "P2P Relay Status",
|
|
281
|
+
description: "Get current P2P mesh connection status, known peers, and identity mapping.",
|
|
282
|
+
parameters: { type: "object", properties: {} },
|
|
283
|
+
async execute(_toolCallId, _params, _ctx) {
|
|
284
|
+
const peerIdentityMap = this.peerIdentityMap;
|
|
285
|
+
const localPeerId = mesh.getLocalPeerId();
|
|
286
|
+
const connectedPeers = mesh.getConnectedPeers();
|
|
287
|
+
const knownIdentities = connectedPeers
|
|
288
|
+
.map((p) => {
|
|
289
|
+
const identity = peerIdentityMap?.resolve(p);
|
|
290
|
+
if (!identity)
|
|
291
|
+
return null;
|
|
292
|
+
return {
|
|
293
|
+
instanceId: identity.instanceId || p.slice(0, 12) + "...",
|
|
294
|
+
agentId: identity.agentId,
|
|
295
|
+
channel: identity.channel,
|
|
296
|
+
};
|
|
297
|
+
})
|
|
298
|
+
.filter(Boolean);
|
|
299
|
+
const text = `P2P Mesh Status:\nLocal Peer ID: ${localPeerId}\nConnected Peers: ${connectedPeers.length}\nKnown Identities: ${knownIdentities.length}`;
|
|
300
|
+
return {
|
|
301
|
+
content: [{ type: "text", text }],
|
|
302
|
+
details: { knownIdentities },
|
|
303
|
+
};
|
|
304
|
+
},
|
|
305
|
+
},
|
|
194
306
|
];
|
|
195
307
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { P2PMessage } from "./types.js";
|
|
2
|
+
import type { PeerIdentityMap } from "./peer-identity.ts";
|
|
3
|
+
export interface IdentityExchangeDeps {
|
|
4
|
+
peerIdentityMap: PeerIdentityMap;
|
|
5
|
+
localPeerId: string;
|
|
6
|
+
localAgentId: string;
|
|
7
|
+
localChannel: string;
|
|
8
|
+
localAccountId: string;
|
|
9
|
+
localInstanceId?: string;
|
|
10
|
+
send: (peerId: string, message: P2PMessage) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function buildIdentityMessage(peerId: string, agentId: string, channel: string, accountId: string, instanceId?: string): P2PMessage;
|
|
13
|
+
export declare function handleIdentityMessage(msg: P2PMessage, deps: IdentityExchangeDeps): Promise<void>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function buildIdentityMessage(peerId, agentId, channel, accountId, instanceId) {
|
|
2
|
+
return {
|
|
3
|
+
id: crypto.randomUUID(),
|
|
4
|
+
type: "identity",
|
|
5
|
+
from: peerId,
|
|
6
|
+
payload: JSON.stringify({ agentId, channel, accountId, instanceId }),
|
|
7
|
+
timestamp: Date.now(),
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export async function handleIdentityMessage(msg, deps) {
|
|
11
|
+
// Parse remote identity payload
|
|
12
|
+
let parsed = {};
|
|
13
|
+
try {
|
|
14
|
+
parsed = JSON.parse(msg.payload);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// Malformed payload — skip silently
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const { agentId, channel, accountId, instanceId } = parsed;
|
|
21
|
+
// Only register if all required fields are present
|
|
22
|
+
if (agentId && channel && accountId) {
|
|
23
|
+
const { buildAgentSessionKey } = await import("openclaw/plugin-sdk/core");
|
|
24
|
+
const sessionKey = buildAgentSessionKey({ agentId, channel, accountId });
|
|
25
|
+
deps.peerIdentityMap.register(msg.from, {
|
|
26
|
+
agentId,
|
|
27
|
+
channel,
|
|
28
|
+
accountId,
|
|
29
|
+
sessionKey,
|
|
30
|
+
instanceId,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// Send local identity back to the remote peer
|
|
34
|
+
const localIdentity = buildIdentityMessage(deps.localPeerId, deps.localAgentId, deps.localChannel, deps.localAccountId, deps.localInstanceId);
|
|
35
|
+
await deps.send(msg.from, localIdentity);
|
|
36
|
+
}
|
package/dist/src/inbound.d.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { P2PMessage } from "./types.js";
|
|
2
|
-
|
|
2
|
+
import type { PeerIdentityMap } from "./peer-identity.ts";
|
|
3
|
+
import type { PluginNextTurnInjection } from "openclaw/plugin-sdk/core";
|
|
4
|
+
export interface InboundHandlerDeps {
|
|
5
|
+
peerIdentityMap?: PeerIdentityMap;
|
|
6
|
+
enqueueNextTurnInjection?: (injection: PluginNextTurnInjection) => Promise<{
|
|
7
|
+
enqueued: boolean;
|
|
8
|
+
id: string;
|
|
9
|
+
}>;
|
|
3
10
|
logger?: {
|
|
4
11
|
info?: (msg: string) => void;
|
|
5
12
|
debug?: (msg: string) => void;
|
|
6
13
|
warn?: (msg: string) => void;
|
|
7
14
|
error?: (msg: string) => void;
|
|
8
15
|
};
|
|
9
|
-
}
|
|
10
|
-
export declare function handleP2PInbound(msg: P2PMessage, deps
|
|
16
|
+
}
|
|
17
|
+
export declare function handleP2PInbound(msg: P2PMessage, deps?: InboundHandlerDeps): void;
|
package/dist/src/inbound.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { verifyInstanceSignature } from "./instance-id.js";
|
|
2
|
+
import { buildAgentSessionKey } from "openclaw/plugin-sdk/core";
|
|
2
3
|
export function handleP2PInbound(msg, deps) {
|
|
3
|
-
const
|
|
4
|
+
const logger = deps?.logger;
|
|
4
5
|
const instanceTag = msg.instanceId ? ` [instance: ${msg.instanceId}]` : "";
|
|
5
6
|
const signedTag = msg.signature ? " [signed]" : "";
|
|
6
7
|
// Verify signature if present
|
|
@@ -15,7 +16,14 @@ export function handleP2PInbound(msg, deps) {
|
|
|
15
16
|
timestamp: msg.timestamp,
|
|
16
17
|
instanceId: msg.instanceId,
|
|
17
18
|
});
|
|
18
|
-
const valid = verifyInstanceSignature({
|
|
19
|
+
const valid = verifyInstanceSignature({
|
|
20
|
+
id: msg.instanceId,
|
|
21
|
+
name: "",
|
|
22
|
+
pubkey: msg.pubkey,
|
|
23
|
+
binding: "",
|
|
24
|
+
bindingComponents: { username: "", hostname: "", platform: "" },
|
|
25
|
+
createdAt: 0,
|
|
26
|
+
}, signedPayload, msg.signature);
|
|
19
27
|
if (valid) {
|
|
20
28
|
logger?.info?.(`[libp2p-mesh] Verified signature from instance ${msg.instanceId}`);
|
|
21
29
|
}
|
|
@@ -26,6 +34,67 @@ export function handleP2PInbound(msg, deps) {
|
|
|
26
34
|
else if (msg.signature) {
|
|
27
35
|
logger?.warn?.(`[libp2p-mesh] Message has signature but no pubkey; cannot verify`);
|
|
28
36
|
}
|
|
37
|
+
// Relay handling when deps are provided with required fields
|
|
38
|
+
if (deps?.peerIdentityMap && deps?.enqueueNextTurnInjection) {
|
|
39
|
+
if (msg.type === "identity") {
|
|
40
|
+
// Parse remote identity payload and register synchronously
|
|
41
|
+
let parsed = {};
|
|
42
|
+
try {
|
|
43
|
+
parsed = JSON.parse(msg.payload);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Malformed payload — skip silently
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const { agentId, channel, accountId, instanceId } = parsed;
|
|
50
|
+
if (agentId && channel && accountId) {
|
|
51
|
+
const sessionKey = buildAgentSessionKey({ agentId, channel, accountId });
|
|
52
|
+
deps.peerIdentityMap.register(msg.from, {
|
|
53
|
+
agentId,
|
|
54
|
+
channel,
|
|
55
|
+
accountId,
|
|
56
|
+
sessionKey,
|
|
57
|
+
instanceId,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if (msg.type === "direct") {
|
|
62
|
+
if (!msg.to) {
|
|
63
|
+
logger?.warn?.("[libp2p-mesh] Direct message missing 'to' field");
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
const receiver = deps.peerIdentityMap.resolve(msg.to);
|
|
67
|
+
if (!receiver) {
|
|
68
|
+
logger?.warn?.(`[libp2p-mesh] Unknown recipient peer: ${msg.to}`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
const sessionKey = buildAgentSessionKey({
|
|
72
|
+
agentId: receiver.agentId,
|
|
73
|
+
channel: receiver.channel,
|
|
74
|
+
accountId: receiver.accountId,
|
|
75
|
+
});
|
|
76
|
+
const text = `【来自P2P网络的消息】\n来源:${msg.from}\n内容:${msg.payload}\n\n` +
|
|
77
|
+
`请你通过你当前的 channel 将此消息转发给你所服务的用户。转发完成后,请务必调用 p2p_send_message 工具向 ${msg.from} 回复送达确认。`;
|
|
78
|
+
deps.enqueueNextTurnInjection({ sessionKey, text, placement: "prepend_context" });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else if (msg.type === "broadcast") {
|
|
83
|
+
const topic = msg.topic || "(none)";
|
|
84
|
+
const text = `【来自P2P网络的广播消息】\n话题:${topic}\n来源:${msg.from}\n内容:${msg.payload}\n\n` +
|
|
85
|
+
`请你通过你当前的 channel 将此广播消息转发给你所服务的用户。`;
|
|
86
|
+
// Inject broadcast into each known peer's session
|
|
87
|
+
for (const [, identity] of deps.peerIdentityMap.entries()) {
|
|
88
|
+
const sessionKey = buildAgentSessionKey({
|
|
89
|
+
agentId: identity.agentId,
|
|
90
|
+
channel: identity.channel,
|
|
91
|
+
accountId: identity.accountId,
|
|
92
|
+
});
|
|
93
|
+
deps.enqueueNextTurnInjection({ sessionKey, text, placement: "prepend_context" });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Original logging (unchanged)
|
|
29
98
|
if (msg.type === "broadcast") {
|
|
30
99
|
logger?.info?.(`[libp2p-mesh] Broadcast from ${msg.from}${instanceTag}${signedTag} on topic ${msg.topic ?? "(none)"}: ${msg.payload}`);
|
|
31
100
|
}
|
package/dist/src/mesh.js
CHANGED
|
@@ -214,11 +214,11 @@ export function createMeshNetwork(options) {
|
|
|
214
214
|
});
|
|
215
215
|
state.node.addEventListener("peer:connect", (evt) => {
|
|
216
216
|
const peerIdStr = evt.detail.toString();
|
|
217
|
-
logger?.
|
|
217
|
+
logger?.info?.(`[libp2p-mesh] Peer connected: ${peerIdStr}`);
|
|
218
218
|
});
|
|
219
219
|
state.node.addEventListener("peer:disconnect", (evt) => {
|
|
220
220
|
const peerIdStr = evt.detail.toString();
|
|
221
|
-
logger?.
|
|
221
|
+
logger?.info?.(`[libp2p-mesh] Peer disconnected: ${peerIdStr}`);
|
|
222
222
|
});
|
|
223
223
|
await state.node.handle(PROTOCOL, async ({ stream, connection }) => {
|
|
224
224
|
try {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface PeerIdentity {
|
|
2
|
+
agentId: string;
|
|
3
|
+
channel: string;
|
|
4
|
+
accountId: string;
|
|
5
|
+
sessionKey: string;
|
|
6
|
+
/** Human-readable Instance ID (e.g. "alice-mac@AQIDBAU.7a3f9e2b") */
|
|
7
|
+
instanceId?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface PeerIdentityMap {
|
|
10
|
+
register(peerId: string, identity: PeerIdentity): void;
|
|
11
|
+
resolve(peerId: string): PeerIdentity | undefined;
|
|
12
|
+
resolveSessionKey(peerId: string): string | undefined;
|
|
13
|
+
resolveByInstanceId(instanceId: string): {
|
|
14
|
+
peerId: string;
|
|
15
|
+
identity: PeerIdentity;
|
|
16
|
+
} | undefined;
|
|
17
|
+
unregister(peerId: string): void;
|
|
18
|
+
setLocalIdentity(peerId: string, identity: PeerIdentity): void;
|
|
19
|
+
getLocalIdentity(): PeerIdentity | undefined;
|
|
20
|
+
hasIdentity(peerId: string): boolean;
|
|
21
|
+
entries(): IterableIterator<[string, PeerIdentity]>;
|
|
22
|
+
}
|
|
23
|
+
export declare function createPeerIdentityMap(): PeerIdentityMap;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function createPeerIdentityMap() {
|
|
2
|
+
const peers = new Map();
|
|
3
|
+
const byInstanceId = new Map();
|
|
4
|
+
let localPeerId;
|
|
5
|
+
let localIdentity;
|
|
6
|
+
return {
|
|
7
|
+
register(peerId, identity) {
|
|
8
|
+
peers.set(peerId, identity);
|
|
9
|
+
if (identity.instanceId) {
|
|
10
|
+
byInstanceId.set(identity.instanceId, { peerId, identity });
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
resolve(peerId) {
|
|
14
|
+
return peers.get(peerId);
|
|
15
|
+
},
|
|
16
|
+
resolveSessionKey(peerId) {
|
|
17
|
+
return peers.get(peerId)?.sessionKey;
|
|
18
|
+
},
|
|
19
|
+
resolveByInstanceId(instanceId) {
|
|
20
|
+
return byInstanceId.get(instanceId);
|
|
21
|
+
},
|
|
22
|
+
unregister(peerId) {
|
|
23
|
+
const identity = peers.get(peerId);
|
|
24
|
+
if (identity?.instanceId) {
|
|
25
|
+
byInstanceId.delete(identity.instanceId);
|
|
26
|
+
}
|
|
27
|
+
peers.delete(peerId);
|
|
28
|
+
},
|
|
29
|
+
setLocalIdentity(peerId, identity) {
|
|
30
|
+
localPeerId = peerId;
|
|
31
|
+
localIdentity = identity;
|
|
32
|
+
peers.set(peerId, identity);
|
|
33
|
+
if (identity.instanceId) {
|
|
34
|
+
byInstanceId.set(identity.instanceId, { peerId, identity });
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
getLocalIdentity() {
|
|
38
|
+
return localIdentity;
|
|
39
|
+
},
|
|
40
|
+
hasIdentity(peerId) {
|
|
41
|
+
return peers.has(peerId);
|
|
42
|
+
},
|
|
43
|
+
entries() {
|
|
44
|
+
return peers.entries();
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|