libp2p-mesh 2026.5.29 → 2026.5.31
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/identity-exchange.js +4 -0
- package/dist/src/inbound.js +1 -0
- package/dist/src/plugin.js +49 -4
- package/package.json +1 -1
- package/src/identity-exchange.ts +4 -0
- package/src/inbound.ts +1 -0
- package/src/plugin.ts +52 -4
|
@@ -9,6 +9,7 @@ export function buildIdentityMessage(peerId, agentId, channel, accountId, instan
|
|
|
9
9
|
}
|
|
10
10
|
export async function handleIdentityMessage(msg, deps) {
|
|
11
11
|
const logger = deps.logger;
|
|
12
|
+
logger?.debug?.(`[libp2p-mesh] handleIdentityMessage: from=${msg.from}, type=${msg.type}, payload_prefix=${msg.payload.slice(0, 60)}`);
|
|
12
13
|
// Parse remote identity payload
|
|
13
14
|
let parsed = {};
|
|
14
15
|
try {
|
|
@@ -31,6 +32,7 @@ export async function handleIdentityMessage(msg, deps) {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
const { agentId, channel, accountId, instanceId } = parsed;
|
|
35
|
+
logger?.debug?.(`[libp2p-mesh] handleIdentityMessage: parsed agentId=${agentId}, channel=${channel}, accountId=${accountId}, instanceId=${instanceId}`);
|
|
34
36
|
// Only register if all required fields are present
|
|
35
37
|
if (agentId && channel && accountId) {
|
|
36
38
|
const { buildAgentSessionKey } = await import("openclaw/plugin-sdk/core");
|
|
@@ -46,6 +48,7 @@ export async function handleIdentityMessage(msg, deps) {
|
|
|
46
48
|
// process exits before the next save cycle.
|
|
47
49
|
await deps.peerIdentityMap.saveNow();
|
|
48
50
|
logger?.info?.(`[libp2p-mesh] Registered peer identity: ${msg.from} (agent=${agentId}, channel=${channel}, instanceId=${instanceId ?? "n/a"})`);
|
|
51
|
+
logger?.debug?.(`[libp2p-mesh] handleIdentityMessage: successfully registered ${msg.from}, now sending reply to ${msg.from}`);
|
|
49
52
|
}
|
|
50
53
|
// Send local identity back to the remote peer.
|
|
51
54
|
// We intentionally do NOT await this — the send callback (and any
|
|
@@ -54,6 +57,7 @@ export async function handleIdentityMessage(msg, deps) {
|
|
|
54
57
|
// blocks the caller; the recipient's handler will persist the reply on
|
|
55
58
|
// its own via saveNow().
|
|
56
59
|
const localIdentity = buildIdentityMessage(deps.localPeerId, deps.localAgentId, deps.localChannel, deps.localAccountId, deps.localInstanceId);
|
|
60
|
+
logger?.debug?.(`[libp2p-mesh] handleIdentityMessage: sending identity reply to ${msg.from}`);
|
|
57
61
|
deps.send(msg.from, localIdentity).catch((sendErr) => {
|
|
58
62
|
logger?.warn?.(`[libp2p-mesh] Failed to send identity reply to ${msg.from}: ${String(sendErr)}`);
|
|
59
63
|
});
|
package/dist/src/inbound.js
CHANGED
|
@@ -41,6 +41,7 @@ export async function handleP2PInbound(msg, deps) {
|
|
|
41
41
|
// Delegate full identity exchange (register + send reply) to the
|
|
42
42
|
// dedicated handler. This ensures bidirectional identity propagation,
|
|
43
43
|
// forced persistence via saveNow(), and proper logging.
|
|
44
|
+
logger?.debug?.(`[libp2p-mesh] handleP2PInbound: processing identity from ${msg.from}`);
|
|
44
45
|
try {
|
|
45
46
|
await handleIdentityMessage(msg, {
|
|
46
47
|
peerIdentityMap: deps.peerIdentityMap,
|
package/dist/src/plugin.js
CHANGED
|
@@ -26,6 +26,12 @@ export function registerLibp2pMesh(api) {
|
|
|
26
26
|
// peers connect during mesh.start() (mDNS can fire peer:connect
|
|
27
27
|
// before this function's sequential code reaches onPeerConnect).
|
|
28
28
|
await peerIdentityMap.load();
|
|
29
|
+
// Queue identity sends during mesh.start() so they are flushed only
|
|
30
|
+
// after the node is fully ready (protocol handler registered, DHT
|
|
31
|
+
// initialized, etc.). Sending inside onPeerConnect during startup
|
|
32
|
+
// races with node initialization and can cause the remote peer's
|
|
33
|
+
// reply to be lost or unprocessed.
|
|
34
|
+
const pendingIdentityPeers = new Set();
|
|
29
35
|
await mesh.start();
|
|
30
36
|
const instanceIdentity = mesh.getInstanceIdentity();
|
|
31
37
|
const localInstanceId = instanceIdentity?.id;
|
|
@@ -79,14 +85,21 @@ export function registerLibp2pMesh(api) {
|
|
|
79
85
|
mesh.onMessage((msg) => {
|
|
80
86
|
handleP2PInbound(msg, buildInboundDeps());
|
|
81
87
|
});
|
|
82
|
-
//
|
|
88
|
+
// Always send our identity when a peer connects. During mesh.start(),
|
|
89
|
+
// mDNS can fire peer:connect before the node is fully ready (protocol
|
|
90
|
+
// handler just registered, DHT still initializing). Sending identity
|
|
91
|
+
// messages at that point can cause the remote peer's reply to arrive
|
|
92
|
+
// before we're ready to process it, breaking the identity exchange.
|
|
93
|
+
// Queue during startup; flush after start() + send directly thereafter.
|
|
94
|
+
let startupComplete = false;
|
|
83
95
|
mesh.onPeerConnect((peerId) => {
|
|
84
|
-
if (
|
|
85
|
-
|
|
96
|
+
if (!startupComplete) {
|
|
97
|
+
// During startup: queue, don't send yet
|
|
98
|
+
pendingIdentityPeers.add(peerId);
|
|
86
99
|
return;
|
|
87
100
|
}
|
|
101
|
+
// After startup: send directly
|
|
88
102
|
api.logger.info?.(`[libp2p-mesh] Peer connected: ${peerId} — sending identity`);
|
|
89
|
-
// Only send identity if relay config is set
|
|
90
103
|
if (!relayChannel || !relayAccountId)
|
|
91
104
|
return;
|
|
92
105
|
const msg = JSON.stringify({
|
|
@@ -140,6 +153,38 @@ export function registerLibp2pMesh(api) {
|
|
|
140
153
|
if (nat.reservedRelays.length > 0) {
|
|
141
154
|
api.logger.info?.(`[libp2p-mesh] Active relay reservations: ${nat.reservedRelays.join(", ")}`);
|
|
142
155
|
}
|
|
156
|
+
// Flush identity messages that were queued during mesh.start().
|
|
157
|
+
// Peers that connected during startup (mDNS-triggered peer:connect
|
|
158
|
+
// inside start()) are in pendingIdentityPeers. Send now that the
|
|
159
|
+
// node is fully ready.
|
|
160
|
+
startupComplete = true;
|
|
161
|
+
const flushPendingIdentities = () => {
|
|
162
|
+
if (!relayChannel || !relayAccountId)
|
|
163
|
+
return;
|
|
164
|
+
const identityMsg = JSON.stringify({
|
|
165
|
+
id: crypto.randomUUID(),
|
|
166
|
+
type: "identity",
|
|
167
|
+
from: mesh.getLocalPeerId(),
|
|
168
|
+
payload: JSON.stringify({
|
|
169
|
+
agentId: api.name,
|
|
170
|
+
channel: relayChannel,
|
|
171
|
+
accountId: relayAccountId,
|
|
172
|
+
instanceId: localInstanceId,
|
|
173
|
+
}),
|
|
174
|
+
timestamp: Date.now(),
|
|
175
|
+
});
|
|
176
|
+
for (const peerId of pendingIdentityPeers) {
|
|
177
|
+
if (peerIdentityMap.hasIdentity(peerId)) {
|
|
178
|
+
api.logger.debug?.(`[libp2p-mesh] Peer ${peerId} already known (loaded from disk), skipping queued identity`);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
mesh.sendToPeer(peerId, identityMsg).catch(() => { });
|
|
182
|
+
}
|
|
183
|
+
pendingIdentityPeers.clear();
|
|
184
|
+
};
|
|
185
|
+
if (pendingIdentityPeers.size > 0) {
|
|
186
|
+
flushPendingIdentities();
|
|
187
|
+
}
|
|
143
188
|
},
|
|
144
189
|
stop: async () => {
|
|
145
190
|
await mesh.stop();
|
package/package.json
CHANGED
package/src/identity-exchange.ts
CHANGED
|
@@ -38,6 +38,7 @@ export async function handleIdentityMessage(
|
|
|
38
38
|
deps: IdentityExchangeDeps,
|
|
39
39
|
): Promise<void> {
|
|
40
40
|
const logger = deps.logger;
|
|
41
|
+
logger?.debug?.(`[libp2p-mesh] handleIdentityMessage: from=${msg.from}, type=${msg.type}, payload_prefix=${msg.payload.slice(0, 60)}`);
|
|
41
42
|
|
|
42
43
|
// Parse remote identity payload
|
|
43
44
|
let parsed: Record<string, unknown> = {};
|
|
@@ -66,6 +67,7 @@ export async function handleIdentityMessage(
|
|
|
66
67
|
accountId?: string;
|
|
67
68
|
instanceId?: string;
|
|
68
69
|
};
|
|
70
|
+
logger?.debug?.(`[libp2p-mesh] handleIdentityMessage: parsed agentId=${agentId}, channel=${channel}, accountId=${accountId}, instanceId=${instanceId}`);
|
|
69
71
|
|
|
70
72
|
// Only register if all required fields are present
|
|
71
73
|
if (agentId && channel && accountId) {
|
|
@@ -84,6 +86,7 @@ export async function handleIdentityMessage(
|
|
|
84
86
|
logger?.info?.(
|
|
85
87
|
`[libp2p-mesh] Registered peer identity: ${msg.from} (agent=${agentId}, channel=${channel}, instanceId=${instanceId ?? "n/a"})`,
|
|
86
88
|
);
|
|
89
|
+
logger?.debug?.(`[libp2p-mesh] handleIdentityMessage: successfully registered ${msg.from}, now sending reply to ${msg.from}`);
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
// Send local identity back to the remote peer.
|
|
@@ -99,6 +102,7 @@ export async function handleIdentityMessage(
|
|
|
99
102
|
deps.localAccountId,
|
|
100
103
|
deps.localInstanceId,
|
|
101
104
|
);
|
|
105
|
+
logger?.debug?.(`[libp2p-mesh] handleIdentityMessage: sending identity reply to ${msg.from}`);
|
|
102
106
|
deps.send(msg.from, localIdentity).catch((sendErr) => {
|
|
103
107
|
logger?.warn?.(
|
|
104
108
|
`[libp2p-mesh] Failed to send identity reply to ${msg.from}: ${String(sendErr)}`,
|
package/src/inbound.ts
CHANGED
|
@@ -60,6 +60,7 @@ export async function handleP2PInbound(msg: P2PMessage, deps?: InboundHandlerDep
|
|
|
60
60
|
// Delegate full identity exchange (register + send reply) to the
|
|
61
61
|
// dedicated handler. This ensures bidirectional identity propagation,
|
|
62
62
|
// forced persistence via saveNow(), and proper logging.
|
|
63
|
+
logger?.debug?.(`[libp2p-mesh] handleP2PInbound: processing identity from ${msg.from}`);
|
|
63
64
|
try {
|
|
64
65
|
await handleIdentityMessage(msg, {
|
|
65
66
|
peerIdentityMap: deps.peerIdentityMap,
|
package/src/plugin.ts
CHANGED
|
@@ -35,6 +35,13 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
35
35
|
// before this function's sequential code reaches onPeerConnect).
|
|
36
36
|
await peerIdentityMap.load();
|
|
37
37
|
|
|
38
|
+
// Queue identity sends during mesh.start() so they are flushed only
|
|
39
|
+
// after the node is fully ready (protocol handler registered, DHT
|
|
40
|
+
// initialized, etc.). Sending inside onPeerConnect during startup
|
|
41
|
+
// races with node initialization and can cause the remote peer's
|
|
42
|
+
// reply to be lost or unprocessed.
|
|
43
|
+
const pendingIdentityPeers = new Set<string>();
|
|
44
|
+
|
|
38
45
|
await mesh.start();
|
|
39
46
|
|
|
40
47
|
const instanceIdentity = mesh.getInstanceIdentity();
|
|
@@ -94,14 +101,22 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
94
101
|
handleP2PInbound(msg, buildInboundDeps());
|
|
95
102
|
});
|
|
96
103
|
|
|
97
|
-
//
|
|
104
|
+
// Always send our identity when a peer connects. During mesh.start(),
|
|
105
|
+
// mDNS can fire peer:connect before the node is fully ready (protocol
|
|
106
|
+
// handler just registered, DHT still initializing). Sending identity
|
|
107
|
+
// messages at that point can cause the remote peer's reply to arrive
|
|
108
|
+
// before we're ready to process it, breaking the identity exchange.
|
|
109
|
+
// Queue during startup; flush after start() + send directly thereafter.
|
|
110
|
+
let startupComplete = false;
|
|
111
|
+
|
|
98
112
|
mesh.onPeerConnect((peerId: string) => {
|
|
99
|
-
if (
|
|
100
|
-
|
|
113
|
+
if (!startupComplete) {
|
|
114
|
+
// During startup: queue, don't send yet
|
|
115
|
+
pendingIdentityPeers.add(peerId);
|
|
101
116
|
return;
|
|
102
117
|
}
|
|
118
|
+
// After startup: send directly
|
|
103
119
|
api.logger.info?.(`[libp2p-mesh] Peer connected: ${peerId} — sending identity`);
|
|
104
|
-
// Only send identity if relay config is set
|
|
105
120
|
if (!relayChannel || !relayAccountId) return;
|
|
106
121
|
const msg = JSON.stringify({
|
|
107
122
|
id: crypto.randomUUID(),
|
|
@@ -160,6 +175,39 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
160
175
|
`[libp2p-mesh] Active relay reservations: ${nat.reservedRelays.join(", ")}`,
|
|
161
176
|
);
|
|
162
177
|
}
|
|
178
|
+
|
|
179
|
+
// Flush identity messages that were queued during mesh.start().
|
|
180
|
+
// Peers that connected during startup (mDNS-triggered peer:connect
|
|
181
|
+
// inside start()) are in pendingIdentityPeers. Send now that the
|
|
182
|
+
// node is fully ready.
|
|
183
|
+
startupComplete = true;
|
|
184
|
+
const flushPendingIdentities = () => {
|
|
185
|
+
if (!relayChannel || !relayAccountId) return;
|
|
186
|
+
const identityMsg = JSON.stringify({
|
|
187
|
+
id: crypto.randomUUID(),
|
|
188
|
+
type: "identity",
|
|
189
|
+
from: mesh.getLocalPeerId(),
|
|
190
|
+
payload: JSON.stringify({
|
|
191
|
+
agentId: api.name,
|
|
192
|
+
channel: relayChannel,
|
|
193
|
+
accountId: relayAccountId,
|
|
194
|
+
instanceId: localInstanceId,
|
|
195
|
+
}),
|
|
196
|
+
timestamp: Date.now(),
|
|
197
|
+
});
|
|
198
|
+
for (const peerId of pendingIdentityPeers) {
|
|
199
|
+
if (peerIdentityMap.hasIdentity(peerId)) {
|
|
200
|
+
api.logger.debug?.(`[libp2p-mesh] Peer ${peerId} already known (loaded from disk), skipping queued identity`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
mesh.sendToPeer(peerId, identityMsg).catch(() => {});
|
|
204
|
+
}
|
|
205
|
+
pendingIdentityPeers.clear();
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
if (pendingIdentityPeers.size > 0) {
|
|
209
|
+
flushPendingIdentities();
|
|
210
|
+
}
|
|
163
211
|
},
|
|
164
212
|
stop: async () => {
|
|
165
213
|
await mesh.stop();
|