libp2p-mesh 2026.5.27 → 2026.5.29
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/dist/src/inbound.js +2 -31
- package/dist/src/plugin.js +79 -134
- package/package.json +1 -1
- package/src/inbound.ts +6 -35
- package/src/plugin.ts +82 -163
package/dist/src/inbound.js
CHANGED
|
@@ -120,25 +120,8 @@ export async function handleP2PInbound(msg, deps) {
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
-
// If the sender is not yet registered, register them synchronously
|
|
124
|
-
// (in-memory only) so that subsequent message routing works correctly.
|
|
125
|
-
// This handles NAT/relay scenarios where peer:connect may not fire
|
|
126
|
-
// bidirectionally, causing the identity exchange to be one-sided.
|
|
127
|
-
// Persistence (saveNow) is fire-and-forget to avoid blocking the
|
|
128
|
-
// message handler.
|
|
129
123
|
if (!deps.peerIdentityMap.hasIdentity(msg.from)) {
|
|
130
|
-
|
|
131
|
-
if (localIdentity) {
|
|
132
|
-
deps.peerIdentityMap.registerSync(msg.from, {
|
|
133
|
-
agentId: localIdentity.agentId,
|
|
134
|
-
channel: localIdentity.channel,
|
|
135
|
-
accountId: localIdentity.accountId,
|
|
136
|
-
sessionKey: "",
|
|
137
|
-
instanceId: msg.instanceId,
|
|
138
|
-
});
|
|
139
|
-
deps.peerIdentityMap.saveNow().catch(() => { });
|
|
140
|
-
logger?.debug?.(`[libp2p-mesh] Auto-registered unknown sender ${msg.from} from direct message`);
|
|
141
|
-
}
|
|
124
|
+
logger?.debug?.(`[libp2p-mesh] Unknown sender ${msg.from}, waiting for identity exchange`);
|
|
142
125
|
}
|
|
143
126
|
}
|
|
144
127
|
else if (msg.type === "broadcast") {
|
|
@@ -171,20 +154,8 @@ export async function handleP2PInbound(msg, deps) {
|
|
|
171
154
|
});
|
|
172
155
|
deps.enqueueNextTurnInjection({ sessionKey, text, placement: "prepend_context" });
|
|
173
156
|
}
|
|
174
|
-
// If the sender is not yet registered, register them synchronously
|
|
175
157
|
if (!deps.peerIdentityMap.hasIdentity(msg.from)) {
|
|
176
|
-
|
|
177
|
-
if (localIdentity) {
|
|
178
|
-
deps.peerIdentityMap.registerSync(msg.from, {
|
|
179
|
-
agentId: localIdentity.agentId,
|
|
180
|
-
channel: localIdentity.channel,
|
|
181
|
-
accountId: localIdentity.accountId,
|
|
182
|
-
sessionKey: "",
|
|
183
|
-
instanceId: msg.instanceId,
|
|
184
|
-
});
|
|
185
|
-
deps.peerIdentityMap.saveNow().catch(() => { });
|
|
186
|
-
logger?.debug?.(`[libp2p-mesh] Auto-registered unknown sender ${msg.from} from broadcast`);
|
|
187
|
-
}
|
|
158
|
+
logger?.debug?.(`[libp2p-mesh] Unknown sender ${msg.from}, waiting for identity exchange`);
|
|
188
159
|
}
|
|
189
160
|
}
|
|
190
161
|
}
|
package/dist/src/plugin.js
CHANGED
|
@@ -1,99 +1,40 @@
|
|
|
1
1
|
import { createLibp2pMeshChannel } from "./channel.js";
|
|
2
2
|
import { handleP2PInbound } from "./inbound.js";
|
|
3
|
-
import { handleIdentityMessage } from "./identity-exchange.js";
|
|
4
3
|
import { createMeshNetwork } from "./mesh.js";
|
|
5
4
|
import { buildP2PTools } from "./agent-tools.js";
|
|
6
5
|
import { createPeerIdentityMap } from "./peer-identity.js";
|
|
7
|
-
import { buildIdentityMessage } from "./identity-exchange.js";
|
|
8
6
|
export function registerLibp2pMesh(api) {
|
|
9
7
|
const mesh = createMeshNetwork({
|
|
10
8
|
config: api.pluginConfig,
|
|
11
9
|
logger: api.logger,
|
|
12
10
|
});
|
|
13
11
|
// Singleton: maps peerId -> { agentId, channel, accountId, sessionKey }
|
|
14
|
-
// Declared here (not inside start) so tool registration below can attach
|
|
15
|
-
// it to tool objects via closure.
|
|
16
12
|
const peerIdentityMap = createPeerIdentityMap();
|
|
13
|
+
// Helper: build the deps object for the relay-aware inbound handler
|
|
14
|
+
function buildInboundDeps() {
|
|
15
|
+
return {
|
|
16
|
+
peerIdentityMap,
|
|
17
|
+
enqueueNextTurnInjection: (injection) => api.session.workflow.enqueueNextTurnInjection(injection),
|
|
18
|
+
logger: api.logger,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
17
21
|
// 1. Register Service (manages libp2p node lifecycle)
|
|
18
22
|
api.registerService({
|
|
19
23
|
id: "libp2p-mesh",
|
|
20
24
|
start: async () => {
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const relayAccountId = config?.relayAccountId;
|
|
25
|
-
function buildInboundDeps() {
|
|
26
|
-
return {
|
|
27
|
-
peerIdentityMap,
|
|
28
|
-
enqueueNextTurnInjection: (injection) => api.session.workflow.enqueueNextTurnInjection(injection),
|
|
29
|
-
logger: api.logger,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
// Wire up relay-aware message handler BEFORE mesh.start() so that
|
|
33
|
-
// any identity messages arriving during startup are queued by libp2p
|
|
34
|
-
// and processed as soon as the protocol handler is ready.
|
|
35
|
-
mesh.onMessage((msg) => {
|
|
36
|
-
if (msg.type === "identity") {
|
|
37
|
-
const localIdentity = peerIdentityMap.getLocalIdentity();
|
|
38
|
-
handleIdentityMessage(msg, {
|
|
39
|
-
peerIdentityMap,
|
|
40
|
-
localPeerId: localIdentity?.sessionKey ?? "",
|
|
41
|
-
localAgentId: api.name,
|
|
42
|
-
localChannel: relayChannel ?? "",
|
|
43
|
-
localAccountId: relayAccountId ?? "",
|
|
44
|
-
localInstanceId: localIdentity?.instanceId,
|
|
45
|
-
send: async (targetPeerId, replyMsg) => {
|
|
46
|
-
mesh.sendToPeer(targetPeerId, JSON.stringify(replyMsg)).catch(() => { });
|
|
47
|
-
},
|
|
48
|
-
logger: api.logger,
|
|
49
|
-
}).catch((err) => {
|
|
50
|
-
api.logger.error?.(`[libp2p-mesh] Identity exchange error: ${String(err)}`);
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
else if (msg.type === "direct") {
|
|
54
|
-
// sendToPeer 包装了一层 direct 外壳,原始 identity 消息藏在 payload 里。
|
|
55
|
-
// 检测并处理:注册对方 + 发送自己的身份回复。
|
|
56
|
-
let handled = false;
|
|
57
|
-
try {
|
|
58
|
-
const raw = JSON.parse(msg.payload);
|
|
59
|
-
if (raw && raw.type === "identity") {
|
|
60
|
-
const localIdentity = peerIdentityMap.getLocalIdentity();
|
|
61
|
-
handleIdentityMessage(msg, {
|
|
62
|
-
peerIdentityMap,
|
|
63
|
-
localPeerId: localIdentity?.sessionKey ?? "",
|
|
64
|
-
localAgentId: api.name,
|
|
65
|
-
localChannel: relayChannel ?? "",
|
|
66
|
-
localAccountId: relayAccountId ?? "",
|
|
67
|
-
localInstanceId: localIdentity?.instanceId,
|
|
68
|
-
send: async (targetPeerId, replyMsg) => {
|
|
69
|
-
mesh.sendToPeer(targetPeerId, JSON.stringify(replyMsg)).catch(() => { });
|
|
70
|
-
},
|
|
71
|
-
logger: api.logger,
|
|
72
|
-
}).catch((err) => {
|
|
73
|
-
api.logger.error?.(`[libp2p-mesh] Identity exchange error: ${String(err)}`);
|
|
74
|
-
});
|
|
75
|
-
handled = true;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
// payload 不是 JSON,正常走 direct 消息逻辑
|
|
80
|
-
}
|
|
81
|
-
if (!handled) {
|
|
82
|
-
handleP2PInbound(msg, buildInboundDeps());
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
handleP2PInbound(msg, buildInboundDeps());
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
await mesh.start();
|
|
90
|
-
// Load persisted peer identities from disk
|
|
25
|
+
// Load persisted peer identities FIRST so dedup checks work when
|
|
26
|
+
// peers connect during mesh.start() (mDNS can fire peer:connect
|
|
27
|
+
// before this function's sequential code reaches onPeerConnect).
|
|
91
28
|
await peerIdentityMap.load();
|
|
29
|
+
await mesh.start();
|
|
92
30
|
const instanceIdentity = mesh.getInstanceIdentity();
|
|
93
31
|
const localInstanceId = instanceIdentity?.id;
|
|
94
|
-
const localPeerId = mesh.getLocalPeerId();
|
|
95
32
|
// Register local identity so remote peers can route messages back to us
|
|
33
|
+
const config = api.pluginConfig;
|
|
34
|
+
const relayChannel = config?.relayChannel;
|
|
35
|
+
const relayAccountId = config?.relayAccountId;
|
|
96
36
|
if (relayChannel && relayAccountId) {
|
|
37
|
+
const localPeerId = mesh.getLocalPeerId();
|
|
97
38
|
const { buildAgentSessionKey } = await import("openclaw/plugin-sdk/core");
|
|
98
39
|
const sessionKey = buildAgentSessionKey({
|
|
99
40
|
agentId: api.name,
|
|
@@ -107,81 +48,85 @@ export function registerLibp2pMesh(api) {
|
|
|
107
48
|
sessionKey,
|
|
108
49
|
instanceId: localInstanceId,
|
|
109
50
|
});
|
|
110
|
-
|
|
111
|
-
agentId: api.name,
|
|
112
|
-
channel: relayChannel,
|
|
113
|
-
accountId: relayAccountId,
|
|
114
|
-
sessionKey,
|
|
115
|
-
instanceId: localInstanceId,
|
|
116
|
-
});
|
|
117
|
-
api.logger.info?.(`[libp2p-mesh] Local identity registered: agent=${api.name}, channel=${relayChannel}, account=${relayAccountId}, instanceId=${localInstanceId}`);
|
|
51
|
+
api.logger.info?.(`[libp2p-mesh] Local identity registered: agent=${api.name}, channel=${relayChannel}, account=${relayAccountId}`);
|
|
118
52
|
// Announce identity to all currently-connected peers
|
|
119
|
-
|
|
120
|
-
|
|
53
|
+
// setLocalIdentity is called BEFORE this block, and localInstanceId
|
|
54
|
+
// is available from mesh.start(), so the identity message includes
|
|
55
|
+
// the instanceId.
|
|
56
|
+
const identityMsg = JSON.stringify({
|
|
57
|
+
id: crypto.randomUUID(),
|
|
58
|
+
type: "identity",
|
|
59
|
+
from: localPeerId,
|
|
60
|
+
payload: JSON.stringify({
|
|
61
|
+
agentId: api.name,
|
|
62
|
+
channel: relayChannel,
|
|
63
|
+
accountId: relayAccountId,
|
|
64
|
+
instanceId: localInstanceId,
|
|
65
|
+
}),
|
|
66
|
+
timestamp: Date.now(),
|
|
67
|
+
});
|
|
68
|
+
const connectedPeers = mesh.getConnectedPeers();
|
|
69
|
+
for (const peerId of connectedPeers) {
|
|
121
70
|
try {
|
|
122
|
-
await mesh.sendToPeer(peerId,
|
|
71
|
+
await mesh.sendToPeer(peerId, identityMsg);
|
|
123
72
|
}
|
|
124
|
-
catch
|
|
125
|
-
|
|
73
|
+
catch {
|
|
74
|
+
// Best-effort; peer may be stale in the connection list
|
|
126
75
|
}
|
|
127
76
|
}
|
|
128
77
|
}
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
// the error silently, breaking identity exchange entirely.
|
|
78
|
+
// Wire up relay-aware message handler
|
|
79
|
+
mesh.onMessage((msg) => {
|
|
80
|
+
handleP2PInbound(msg, buildInboundDeps());
|
|
81
|
+
});
|
|
82
|
+
// Dedup: skip identity send if peer is already known
|
|
135
83
|
mesh.onPeerConnect((peerId) => {
|
|
84
|
+
if (peerIdentityMap.hasIdentity(peerId)) {
|
|
85
|
+
api.logger.debug?.(`[libp2p-mesh] Peer ${peerId} already known, skipping identity send`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
136
88
|
api.logger.info?.(`[libp2p-mesh] Peer connected: ${peerId} — sending identity`);
|
|
137
|
-
// Only send identity if relay config is set
|
|
138
|
-
// identity to announce).
|
|
89
|
+
// Only send identity if relay config is set
|
|
139
90
|
if (!relayChannel || !relayAccountId)
|
|
140
91
|
return;
|
|
141
|
-
const msg =
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
api.logger.warn?.(`[libp2p-mesh] Failed to send identity to ${peerId} after ${maxRetries} attempts: ${String(err)}`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
sendWithRetry();
|
|
161
|
-
});
|
|
162
|
-
mesh.onPeerDisconnect((peerId) => {
|
|
163
|
-
api.logger.info?.(`[libp2p-mesh] Peer disconnected: ${peerId}`);
|
|
164
|
-
peerIdentityMap.unregister(peerId);
|
|
165
|
-
peerIdentityMap.saveNow().catch(() => { });
|
|
92
|
+
const msg = JSON.stringify({
|
|
93
|
+
id: crypto.randomUUID(),
|
|
94
|
+
type: "identity",
|
|
95
|
+
from: mesh.getLocalPeerId(),
|
|
96
|
+
payload: JSON.stringify({
|
|
97
|
+
agentId: api.name,
|
|
98
|
+
channel: relayChannel,
|
|
99
|
+
accountId: relayAccountId,
|
|
100
|
+
instanceId: localInstanceId,
|
|
101
|
+
}),
|
|
102
|
+
timestamp: Date.now(),
|
|
103
|
+
});
|
|
104
|
+
mesh.sendToPeer(peerId, msg).catch(() => { });
|
|
166
105
|
});
|
|
167
|
-
//
|
|
168
|
-
// This covers the race where mDNS discovery fires peer:connect during
|
|
169
|
-
// node.start() before onPeerConnect was registered — those peers are
|
|
170
|
-
// already connected but never received our identity.
|
|
106
|
+
// Dedup: skip initial identity announce for known peers
|
|
171
107
|
if (relayChannel && relayAccountId) {
|
|
172
|
-
const identityMsg =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
108
|
+
const identityMsg = JSON.stringify({
|
|
109
|
+
id: crypto.randomUUID(),
|
|
110
|
+
type: "identity",
|
|
111
|
+
from: mesh.getLocalPeerId(),
|
|
112
|
+
payload: JSON.stringify({
|
|
113
|
+
agentId: api.name,
|
|
114
|
+
channel: relayChannel,
|
|
115
|
+
accountId: relayAccountId,
|
|
116
|
+
instanceId: localInstanceId,
|
|
117
|
+
}),
|
|
118
|
+
timestamp: Date.now(),
|
|
119
|
+
});
|
|
120
|
+
for (const peerId of mesh.getConnectedPeers()) {
|
|
121
|
+
if (peerIdentityMap.hasIdentity(peerId)) {
|
|
122
|
+
api.logger.debug?.(`[libp2p-mesh] Peer ${peerId} already known, skipping initial identity`);
|
|
123
|
+
continue;
|
|
180
124
|
}
|
|
125
|
+
mesh.sendToPeer(peerId, identityMsg).catch(() => { });
|
|
181
126
|
}
|
|
182
127
|
}
|
|
183
128
|
const identity = mesh.getInstanceIdentity();
|
|
184
|
-
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${
|
|
129
|
+
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${mesh.getLocalPeerId()}`);
|
|
185
130
|
if (identity) {
|
|
186
131
|
api.logger.info?.(`[libp2p-mesh] Instance Identity: ${identity.id}`);
|
|
187
132
|
}
|
package/package.json
CHANGED
package/src/inbound.ts
CHANGED
|
@@ -137,27 +137,10 @@ export async function handleP2PInbound(msg: P2PMessage, deps?: InboundHandlerDep
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
// If the sender is not yet registered, register them synchronously
|
|
141
|
-
// (in-memory only) so that subsequent message routing works correctly.
|
|
142
|
-
// This handles NAT/relay scenarios where peer:connect may not fire
|
|
143
|
-
// bidirectionally, causing the identity exchange to be one-sided.
|
|
144
|
-
// Persistence (saveNow) is fire-and-forget to avoid blocking the
|
|
145
|
-
// message handler.
|
|
146
140
|
if (!deps.peerIdentityMap.hasIdentity(msg.from)) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
agentId: localIdentity.agentId,
|
|
151
|
-
channel: localIdentity.channel,
|
|
152
|
-
accountId: localIdentity.accountId,
|
|
153
|
-
sessionKey: "",
|
|
154
|
-
instanceId: msg.instanceId,
|
|
155
|
-
});
|
|
156
|
-
deps.peerIdentityMap.saveNow().catch(() => {});
|
|
157
|
-
logger?.debug?.(
|
|
158
|
-
`[libp2p-mesh] Auto-registered unknown sender ${msg.from} from direct message`,
|
|
159
|
-
);
|
|
160
|
-
}
|
|
141
|
+
logger?.debug?.(
|
|
142
|
+
`[libp2p-mesh] Unknown sender ${msg.from}, waiting for identity exchange`,
|
|
143
|
+
);
|
|
161
144
|
}
|
|
162
145
|
} else if (msg.type === "broadcast") {
|
|
163
146
|
const topic = msg.topic || "(none)";
|
|
@@ -191,22 +174,10 @@ export async function handleP2PInbound(msg: P2PMessage, deps?: InboundHandlerDep
|
|
|
191
174
|
deps.enqueueNextTurnInjection({ sessionKey, text, placement: "prepend_context" });
|
|
192
175
|
}
|
|
193
176
|
|
|
194
|
-
// If the sender is not yet registered, register them synchronously
|
|
195
177
|
if (!deps.peerIdentityMap.hasIdentity(msg.from)) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
agentId: localIdentity.agentId,
|
|
200
|
-
channel: localIdentity.channel,
|
|
201
|
-
accountId: localIdentity.accountId,
|
|
202
|
-
sessionKey: "",
|
|
203
|
-
instanceId: msg.instanceId,
|
|
204
|
-
});
|
|
205
|
-
deps.peerIdentityMap.saveNow().catch(() => {});
|
|
206
|
-
logger?.debug?.(
|
|
207
|
-
`[libp2p-mesh] Auto-registered unknown sender ${msg.from} from broadcast`,
|
|
208
|
-
);
|
|
209
|
-
}
|
|
178
|
+
logger?.debug?.(
|
|
179
|
+
`[libp2p-mesh] Unknown sender ${msg.from}, waiting for identity exchange`,
|
|
180
|
+
);
|
|
210
181
|
}
|
|
211
182
|
}
|
|
212
183
|
}
|
package/src/plugin.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import type { OpenClawPluginApi
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
2
2
|
import { createLibp2pMeshChannel } from "./channel.js";
|
|
3
3
|
import { handleP2PInbound } from "./inbound.js";
|
|
4
|
-
import { handleIdentityMessage } from "./identity-exchange.js";
|
|
5
4
|
import { createMeshNetwork } from "./mesh.js";
|
|
6
5
|
import { buildP2PTools } from "./agent-tools.js";
|
|
7
6
|
import { createPeerIdentityMap } from "./peer-identity.js";
|
|
8
|
-
import { buildIdentityMessage } from "./identity-exchange.js";
|
|
9
7
|
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
|
|
10
8
|
import type { MeshConfig } from "./types.js";
|
|
11
9
|
|
|
@@ -16,94 +14,38 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
16
14
|
});
|
|
17
15
|
|
|
18
16
|
// Singleton: maps peerId -> { agentId, channel, accountId, sessionKey }
|
|
19
|
-
// Declared here (not inside start) so tool registration below can attach
|
|
20
|
-
// it to tool objects via closure.
|
|
21
17
|
const peerIdentityMap = createPeerIdentityMap();
|
|
22
18
|
|
|
19
|
+
// Helper: build the deps object for the relay-aware inbound handler
|
|
20
|
+
function buildInboundDeps() {
|
|
21
|
+
return {
|
|
22
|
+
peerIdentityMap,
|
|
23
|
+
enqueueNextTurnInjection: (injection: any) =>
|
|
24
|
+
api.session.workflow.enqueueNextTurnInjection(injection),
|
|
25
|
+
logger: api.logger,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
23
29
|
// 1. Register Service (manages libp2p node lifecycle)
|
|
24
30
|
api.registerService({
|
|
25
31
|
id: "libp2p-mesh",
|
|
26
32
|
start: async () => {
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
function buildInboundDeps() {
|
|
33
|
-
return {
|
|
34
|
-
peerIdentityMap,
|
|
35
|
-
enqueueNextTurnInjection: (injection: PluginNextTurnInjection) =>
|
|
36
|
-
api.session.workflow.enqueueNextTurnInjection(injection),
|
|
37
|
-
logger: api.logger,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Wire up relay-aware message handler BEFORE mesh.start() so that
|
|
42
|
-
// any identity messages arriving during startup are queued by libp2p
|
|
43
|
-
// and processed as soon as the protocol handler is ready.
|
|
44
|
-
mesh.onMessage((msg) => {
|
|
45
|
-
if (msg.type === "identity") {
|
|
46
|
-
const localIdentity = peerIdentityMap.getLocalIdentity();
|
|
47
|
-
handleIdentityMessage(msg, {
|
|
48
|
-
peerIdentityMap,
|
|
49
|
-
localPeerId: localIdentity?.sessionKey ?? "",
|
|
50
|
-
localAgentId: api.name,
|
|
51
|
-
localChannel: relayChannel ?? "",
|
|
52
|
-
localAccountId: relayAccountId ?? "",
|
|
53
|
-
localInstanceId: localIdentity?.instanceId,
|
|
54
|
-
send: async (targetPeerId: string, replyMsg: any) => {
|
|
55
|
-
mesh.sendToPeer(targetPeerId, JSON.stringify(replyMsg)).catch(() => {});
|
|
56
|
-
},
|
|
57
|
-
logger: api.logger,
|
|
58
|
-
}).catch((err) => {
|
|
59
|
-
api.logger.error?.(`[libp2p-mesh] Identity exchange error: ${String(err)}`);
|
|
60
|
-
});
|
|
61
|
-
} else if (msg.type === "direct") {
|
|
62
|
-
// sendToPeer 包装了一层 direct 外壳,原始 identity 消息藏在 payload 里。
|
|
63
|
-
// 检测并处理:注册对方 + 发送自己的身份回复。
|
|
64
|
-
let handled = false;
|
|
65
|
-
try {
|
|
66
|
-
const raw = JSON.parse(msg.payload);
|
|
67
|
-
if (raw && raw.type === "identity") {
|
|
68
|
-
const localIdentity = peerIdentityMap.getLocalIdentity();
|
|
69
|
-
handleIdentityMessage(msg, {
|
|
70
|
-
peerIdentityMap,
|
|
71
|
-
localPeerId: localIdentity?.sessionKey ?? "",
|
|
72
|
-
localAgentId: api.name,
|
|
73
|
-
localChannel: relayChannel ?? "",
|
|
74
|
-
localAccountId: relayAccountId ?? "",
|
|
75
|
-
localInstanceId: localIdentity?.instanceId,
|
|
76
|
-
send: async (targetPeerId: string, replyMsg: any) => {
|
|
77
|
-
mesh.sendToPeer(targetPeerId, JSON.stringify(replyMsg)).catch(() => {});
|
|
78
|
-
},
|
|
79
|
-
logger: api.logger,
|
|
80
|
-
}).catch((err) => {
|
|
81
|
-
api.logger.error?.(`[libp2p-mesh] Identity exchange error: ${String(err)}`);
|
|
82
|
-
});
|
|
83
|
-
handled = true;
|
|
84
|
-
}
|
|
85
|
-
} catch {
|
|
86
|
-
// payload 不是 JSON,正常走 direct 消息逻辑
|
|
87
|
-
}
|
|
88
|
-
if (!handled) {
|
|
89
|
-
handleP2PInbound(msg, buildInboundDeps());
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
handleP2PInbound(msg, buildInboundDeps());
|
|
93
|
-
}
|
|
94
|
-
});
|
|
33
|
+
// Load persisted peer identities FIRST so dedup checks work when
|
|
34
|
+
// peers connect during mesh.start() (mDNS can fire peer:connect
|
|
35
|
+
// before this function's sequential code reaches onPeerConnect).
|
|
36
|
+
await peerIdentityMap.load();
|
|
95
37
|
|
|
96
38
|
await mesh.start();
|
|
97
39
|
|
|
98
|
-
// Load persisted peer identities from disk
|
|
99
|
-
await peerIdentityMap.load();
|
|
100
|
-
|
|
101
40
|
const instanceIdentity = mesh.getInstanceIdentity();
|
|
102
41
|
const localInstanceId = instanceIdentity?.id;
|
|
103
|
-
const localPeerId = mesh.getLocalPeerId();
|
|
104
42
|
|
|
105
43
|
// Register local identity so remote peers can route messages back to us
|
|
44
|
+
const config = api.pluginConfig as MeshConfig | undefined;
|
|
45
|
+
const relayChannel = config?.relayChannel;
|
|
46
|
+
const relayAccountId = config?.relayAccountId;
|
|
106
47
|
if (relayChannel && relayAccountId) {
|
|
48
|
+
const localPeerId = mesh.getLocalPeerId();
|
|
107
49
|
const { buildAgentSessionKey } = await import("openclaw/plugin-sdk/core");
|
|
108
50
|
const sessionKey = buildAgentSessionKey({
|
|
109
51
|
agentId: api.name,
|
|
@@ -117,113 +59,90 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
117
59
|
sessionKey,
|
|
118
60
|
instanceId: localInstanceId,
|
|
119
61
|
});
|
|
120
|
-
await peerIdentityMap.register(localPeerId, {
|
|
121
|
-
agentId: api.name,
|
|
122
|
-
channel: relayChannel,
|
|
123
|
-
accountId: relayAccountId,
|
|
124
|
-
sessionKey,
|
|
125
|
-
instanceId: localInstanceId,
|
|
126
|
-
});
|
|
127
62
|
api.logger.info?.(
|
|
128
|
-
`[libp2p-mesh] Local identity registered: agent=${api.name}, channel=${relayChannel}, account=${relayAccountId}
|
|
63
|
+
`[libp2p-mesh] Local identity registered: agent=${api.name}, channel=${relayChannel}, account=${relayAccountId}`,
|
|
129
64
|
);
|
|
130
65
|
|
|
131
66
|
// Announce identity to all currently-connected peers
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
67
|
+
// setLocalIdentity is called BEFORE this block, and localInstanceId
|
|
68
|
+
// is available from mesh.start(), so the identity message includes
|
|
69
|
+
// the instanceId.
|
|
70
|
+
const identityMsg = JSON.stringify({
|
|
71
|
+
id: crypto.randomUUID(),
|
|
72
|
+
type: "identity",
|
|
73
|
+
from: localPeerId,
|
|
74
|
+
payload: JSON.stringify({
|
|
75
|
+
agentId: api.name,
|
|
76
|
+
channel: relayChannel,
|
|
77
|
+
accountId: relayAccountId,
|
|
78
|
+
instanceId: localInstanceId,
|
|
79
|
+
}),
|
|
80
|
+
timestamp: Date.now(),
|
|
81
|
+
});
|
|
82
|
+
const connectedPeers = mesh.getConnectedPeers();
|
|
83
|
+
for (const peerId of connectedPeers) {
|
|
140
84
|
try {
|
|
141
|
-
await mesh.sendToPeer(peerId,
|
|
142
|
-
} catch
|
|
143
|
-
|
|
144
|
-
`[libp2p-mesh] Failed to send initial identity to ${peerId}: ${String(err)}`,
|
|
145
|
-
);
|
|
85
|
+
await mesh.sendToPeer(peerId, identityMsg);
|
|
86
|
+
} catch {
|
|
87
|
+
// Best-effort; peer may be stale in the connection list
|
|
146
88
|
}
|
|
147
89
|
}
|
|
148
90
|
}
|
|
149
91
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// handler isn't fully wired yet — and the .catch(() => {}) swallows
|
|
155
|
-
// the error silently, breaking identity exchange entirely.
|
|
92
|
+
// Wire up relay-aware message handler
|
|
93
|
+
mesh.onMessage((msg) => {
|
|
94
|
+
handleP2PInbound(msg, buildInboundDeps());
|
|
95
|
+
});
|
|
156
96
|
|
|
97
|
+
// Dedup: skip identity send if peer is already known
|
|
157
98
|
mesh.onPeerConnect((peerId: string) => {
|
|
99
|
+
if (peerIdentityMap.hasIdentity(peerId)) {
|
|
100
|
+
api.logger.debug?.(`[libp2p-mesh] Peer ${peerId} already known, skipping identity send`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
158
103
|
api.logger.info?.(`[libp2p-mesh] Peer connected: ${peerId} — sending identity`);
|
|
159
|
-
// Only send identity if relay config is set
|
|
160
|
-
// identity to announce).
|
|
104
|
+
// Only send identity if relay config is set
|
|
161
105
|
if (!relayChannel || !relayAccountId) return;
|
|
162
|
-
const msg =
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
} catch (err) {
|
|
176
|
-
attempt++;
|
|
177
|
-
if (attempt < maxRetries) {
|
|
178
|
-
api.logger.debug?.(
|
|
179
|
-
`[libp2p-mesh] Identity send to ${peerId} failed (attempt ${attempt}/${maxRetries}), retrying...`,
|
|
180
|
-
);
|
|
181
|
-
setTimeout(sendWithRetry, 500);
|
|
182
|
-
} else {
|
|
183
|
-
api.logger.warn?.(
|
|
184
|
-
`[libp2p-mesh] Failed to send identity to ${peerId} after ${maxRetries} attempts: ${String(err)}`,
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
sendWithRetry();
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
mesh.onPeerDisconnect((peerId: string) => {
|
|
193
|
-
api.logger.info?.(`[libp2p-mesh] Peer disconnected: ${peerId}`);
|
|
194
|
-
peerIdentityMap.unregister(peerId);
|
|
195
|
-
peerIdentityMap.saveNow().catch(() => {});
|
|
106
|
+
const msg = JSON.stringify({
|
|
107
|
+
id: crypto.randomUUID(),
|
|
108
|
+
type: "identity",
|
|
109
|
+
from: mesh.getLocalPeerId(),
|
|
110
|
+
payload: JSON.stringify({
|
|
111
|
+
agentId: api.name,
|
|
112
|
+
channel: relayChannel,
|
|
113
|
+
accountId: relayAccountId,
|
|
114
|
+
instanceId: localInstanceId,
|
|
115
|
+
}),
|
|
116
|
+
timestamp: Date.now(),
|
|
117
|
+
});
|
|
118
|
+
mesh.sendToPeer(peerId, msg).catch(() => {});
|
|
196
119
|
});
|
|
197
120
|
|
|
198
|
-
//
|
|
199
|
-
// This covers the race where mDNS discovery fires peer:connect during
|
|
200
|
-
// node.start() before onPeerConnect was registered — those peers are
|
|
201
|
-
// already connected but never received our identity.
|
|
121
|
+
// Dedup: skip initial identity announce for known peers
|
|
202
122
|
if (relayChannel && relayAccountId) {
|
|
203
|
-
const identityMsg =
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
);
|
|
220
|
-
});
|
|
123
|
+
const identityMsg = JSON.stringify({
|
|
124
|
+
id: crypto.randomUUID(),
|
|
125
|
+
type: "identity",
|
|
126
|
+
from: mesh.getLocalPeerId(),
|
|
127
|
+
payload: JSON.stringify({
|
|
128
|
+
agentId: api.name,
|
|
129
|
+
channel: relayChannel,
|
|
130
|
+
accountId: relayAccountId,
|
|
131
|
+
instanceId: localInstanceId,
|
|
132
|
+
}),
|
|
133
|
+
timestamp: Date.now(),
|
|
134
|
+
});
|
|
135
|
+
for (const peerId of mesh.getConnectedPeers()) {
|
|
136
|
+
if (peerIdentityMap.hasIdentity(peerId)) {
|
|
137
|
+
api.logger.debug?.(`[libp2p-mesh] Peer ${peerId} already known, skipping initial identity`);
|
|
138
|
+
continue;
|
|
221
139
|
}
|
|
140
|
+
mesh.sendToPeer(peerId, identityMsg).catch(() => {});
|
|
222
141
|
}
|
|
223
142
|
}
|
|
224
143
|
|
|
225
144
|
const identity = mesh.getInstanceIdentity();
|
|
226
|
-
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${
|
|
145
|
+
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${mesh.getLocalPeerId()}`);
|
|
227
146
|
if (identity) {
|
|
228
147
|
api.logger.info?.(`[libp2p-mesh] Instance Identity: ${identity.id}`);
|
|
229
148
|
}
|