libp2p-mesh 2026.5.27 → 2026.5.28
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 +70 -136
- package/package.json +1 -1
- package/src/inbound.ts +6 -35
- package/src/plugin.ts +73 -166
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,36 @@
|
|
|
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
|
-
// Gather config before starting (needed for onMessage handler)
|
|
22
|
-
const config = api.pluginConfig;
|
|
23
|
-
const relayChannel = config?.relayChannel;
|
|
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
25
|
await mesh.start();
|
|
90
|
-
// Load persisted peer identities
|
|
26
|
+
// Load persisted peer identities so dedup checks work on restart
|
|
91
27
|
await peerIdentityMap.load();
|
|
92
|
-
const instanceIdentity = mesh.getInstanceIdentity();
|
|
93
|
-
const localInstanceId = instanceIdentity?.id;
|
|
94
|
-
const localPeerId = mesh.getLocalPeerId();
|
|
95
28
|
// Register local identity so remote peers can route messages back to us
|
|
29
|
+
const config = api.pluginConfig;
|
|
30
|
+
const relayChannel = config?.relayChannel;
|
|
31
|
+
const relayAccountId = config?.relayAccountId;
|
|
96
32
|
if (relayChannel && relayAccountId) {
|
|
33
|
+
const localPeerId = mesh.getLocalPeerId();
|
|
97
34
|
const { buildAgentSessionKey } = await import("openclaw/plugin-sdk/core");
|
|
98
35
|
const sessionKey = buildAgentSessionKey({
|
|
99
36
|
agentId: api.name,
|
|
@@ -105,83 +42,80 @@ export function registerLibp2pMesh(api) {
|
|
|
105
42
|
channel: relayChannel,
|
|
106
43
|
accountId: relayAccountId,
|
|
107
44
|
sessionKey,
|
|
108
|
-
instanceId: localInstanceId,
|
|
109
45
|
});
|
|
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}`);
|
|
46
|
+
api.logger.info?.(`[libp2p-mesh] Local identity registered: agent=${api.name}, channel=${relayChannel}, account=${relayAccountId}`);
|
|
118
47
|
// Announce identity to all currently-connected peers
|
|
119
|
-
const identityMsg =
|
|
120
|
-
|
|
48
|
+
const identityMsg = JSON.stringify({
|
|
49
|
+
id: crypto.randomUUID(),
|
|
50
|
+
type: "identity",
|
|
51
|
+
from: localPeerId,
|
|
52
|
+
payload: JSON.stringify({
|
|
53
|
+
agentId: api.name,
|
|
54
|
+
channel: relayChannel,
|
|
55
|
+
accountId: relayAccountId,
|
|
56
|
+
}),
|
|
57
|
+
timestamp: Date.now(),
|
|
58
|
+
});
|
|
59
|
+
const connectedPeers = mesh.getConnectedPeers();
|
|
60
|
+
for (const peerId of connectedPeers) {
|
|
121
61
|
try {
|
|
122
|
-
await mesh.sendToPeer(peerId,
|
|
62
|
+
await mesh.sendToPeer(peerId, identityMsg);
|
|
123
63
|
}
|
|
124
|
-
catch
|
|
125
|
-
|
|
64
|
+
catch {
|
|
65
|
+
// Best-effort; peer may be stale in the connection list
|
|
126
66
|
}
|
|
127
67
|
}
|
|
128
68
|
}
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
//
|
|
134
|
-
// the error silently, breaking identity exchange entirely.
|
|
69
|
+
// Wire up relay-aware message handler
|
|
70
|
+
mesh.onMessage((msg) => {
|
|
71
|
+
handleP2PInbound(msg, buildInboundDeps());
|
|
72
|
+
});
|
|
73
|
+
// Dedup: skip identity send if peer is already known
|
|
135
74
|
mesh.onPeerConnect((peerId) => {
|
|
75
|
+
if (peerIdentityMap.hasIdentity(peerId)) {
|
|
76
|
+
api.logger.debug?.(`[libp2p-mesh] Peer ${peerId} already known, skipping identity send`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
136
79
|
api.logger.info?.(`[libp2p-mesh] Peer connected: ${peerId} — sending identity`);
|
|
137
|
-
// Only send identity if relay config is set
|
|
138
|
-
// identity to announce).
|
|
80
|
+
// Only send identity if relay config is set
|
|
139
81
|
if (!relayChannel || !relayAccountId)
|
|
140
82
|
return;
|
|
141
|
-
const msg =
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
setTimeout(sendWithRetry, 500);
|
|
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(() => { });
|
|
83
|
+
const msg = JSON.stringify({
|
|
84
|
+
id: crypto.randomUUID(),
|
|
85
|
+
type: "identity",
|
|
86
|
+
from: mesh.getLocalPeerId(),
|
|
87
|
+
payload: JSON.stringify({
|
|
88
|
+
agentId: api.name,
|
|
89
|
+
channel: relayChannel,
|
|
90
|
+
accountId: relayAccountId,
|
|
91
|
+
}),
|
|
92
|
+
timestamp: Date.now(),
|
|
93
|
+
});
|
|
94
|
+
mesh.sendToPeer(peerId, msg).catch(() => { });
|
|
166
95
|
});
|
|
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.
|
|
96
|
+
// Dedup: skip initial identity announce for known peers
|
|
171
97
|
if (relayChannel && relayAccountId) {
|
|
172
|
-
const identityMsg =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
98
|
+
const identityMsg = JSON.stringify({
|
|
99
|
+
id: crypto.randomUUID(),
|
|
100
|
+
type: "identity",
|
|
101
|
+
from: mesh.getLocalPeerId(),
|
|
102
|
+
payload: JSON.stringify({
|
|
103
|
+
agentId: api.name,
|
|
104
|
+
channel: relayChannel,
|
|
105
|
+
accountId: relayAccountId,
|
|
106
|
+
}),
|
|
107
|
+
timestamp: Date.now(),
|
|
108
|
+
});
|
|
109
|
+
for (const peerId of mesh.getConnectedPeers()) {
|
|
110
|
+
if (peerIdentityMap.hasIdentity(peerId)) {
|
|
111
|
+
api.logger.debug?.(`[libp2p-mesh] Peer ${peerId} already known, skipping initial identity`);
|
|
112
|
+
continue;
|
|
180
113
|
}
|
|
114
|
+
mesh.sendToPeer(peerId, identityMsg).catch(() => { });
|
|
181
115
|
}
|
|
182
116
|
}
|
|
183
117
|
const identity = mesh.getInstanceIdentity();
|
|
184
|
-
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${
|
|
118
|
+
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${mesh.getLocalPeerId()}`);
|
|
185
119
|
if (identity) {
|
|
186
120
|
api.logger.info?.(`[libp2p-mesh] Instance Identity: ${identity.id}`);
|
|
187
121
|
}
|
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,33 @@ 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
|
-
// Gather config before starting (needed for onMessage handler)
|
|
28
|
-
const config = api.pluginConfig as MeshConfig | undefined;
|
|
29
|
-
const relayChannel = config?.relayChannel;
|
|
30
|
-
const relayAccountId = config?.relayAccountId;
|
|
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
|
-
});
|
|
95
|
-
|
|
96
33
|
await mesh.start();
|
|
97
34
|
|
|
98
|
-
// Load persisted peer identities
|
|
35
|
+
// Load persisted peer identities so dedup checks work on restart
|
|
99
36
|
await peerIdentityMap.load();
|
|
100
37
|
|
|
101
|
-
const instanceIdentity = mesh.getInstanceIdentity();
|
|
102
|
-
const localInstanceId = instanceIdentity?.id;
|
|
103
|
-
const localPeerId = mesh.getLocalPeerId();
|
|
104
|
-
|
|
105
38
|
// Register local identity so remote peers can route messages back to us
|
|
39
|
+
const config = api.pluginConfig as MeshConfig | undefined;
|
|
40
|
+
const relayChannel = config?.relayChannel;
|
|
41
|
+
const relayAccountId = config?.relayAccountId;
|
|
106
42
|
if (relayChannel && relayAccountId) {
|
|
43
|
+
const localPeerId = mesh.getLocalPeerId();
|
|
107
44
|
const { buildAgentSessionKey } = await import("openclaw/plugin-sdk/core");
|
|
108
45
|
const sessionKey = buildAgentSessionKey({
|
|
109
46
|
agentId: api.name,
|
|
@@ -115,115 +52,85 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
115
52
|
channel: relayChannel,
|
|
116
53
|
accountId: relayAccountId,
|
|
117
54
|
sessionKey,
|
|
118
|
-
instanceId: localInstanceId,
|
|
119
|
-
});
|
|
120
|
-
await peerIdentityMap.register(localPeerId, {
|
|
121
|
-
agentId: api.name,
|
|
122
|
-
channel: relayChannel,
|
|
123
|
-
accountId: relayAccountId,
|
|
124
|
-
sessionKey,
|
|
125
|
-
instanceId: localInstanceId,
|
|
126
55
|
});
|
|
127
56
|
api.logger.info?.(
|
|
128
|
-
`[libp2p-mesh] Local identity registered: agent=${api.name}, channel=${relayChannel}, account=${relayAccountId}
|
|
57
|
+
`[libp2p-mesh] Local identity registered: agent=${api.name}, channel=${relayChannel}, account=${relayAccountId}`,
|
|
129
58
|
);
|
|
130
59
|
|
|
131
60
|
// Announce identity to all currently-connected peers
|
|
132
|
-
const identityMsg =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
61
|
+
const identityMsg = JSON.stringify({
|
|
62
|
+
id: crypto.randomUUID(),
|
|
63
|
+
type: "identity",
|
|
64
|
+
from: localPeerId,
|
|
65
|
+
payload: JSON.stringify({
|
|
66
|
+
agentId: api.name,
|
|
67
|
+
channel: relayChannel,
|
|
68
|
+
accountId: relayAccountId,
|
|
69
|
+
}),
|
|
70
|
+
timestamp: Date.now(),
|
|
71
|
+
});
|
|
72
|
+
const connectedPeers = mesh.getConnectedPeers();
|
|
73
|
+
for (const peerId of connectedPeers) {
|
|
140
74
|
try {
|
|
141
|
-
await mesh.sendToPeer(peerId,
|
|
142
|
-
} catch
|
|
143
|
-
|
|
144
|
-
`[libp2p-mesh] Failed to send initial identity to ${peerId}: ${String(err)}`,
|
|
145
|
-
);
|
|
75
|
+
await mesh.sendToPeer(peerId, identityMsg);
|
|
76
|
+
} catch {
|
|
77
|
+
// Best-effort; peer may be stale in the connection list
|
|
146
78
|
}
|
|
147
79
|
}
|
|
148
80
|
}
|
|
149
81
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// handler isn't fully wired yet — and the .catch(() => {}) swallows
|
|
155
|
-
// the error silently, breaking identity exchange entirely.
|
|
82
|
+
// Wire up relay-aware message handler
|
|
83
|
+
mesh.onMessage((msg) => {
|
|
84
|
+
handleP2PInbound(msg, buildInboundDeps());
|
|
85
|
+
});
|
|
156
86
|
|
|
87
|
+
// Dedup: skip identity send if peer is already known
|
|
157
88
|
mesh.onPeerConnect((peerId: string) => {
|
|
89
|
+
if (peerIdentityMap.hasIdentity(peerId)) {
|
|
90
|
+
api.logger.debug?.(`[libp2p-mesh] Peer ${peerId} already known, skipping identity send`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
158
93
|
api.logger.info?.(`[libp2p-mesh] Peer connected: ${peerId} — sending identity`);
|
|
159
|
-
// Only send identity if relay config is set
|
|
160
|
-
// identity to announce).
|
|
94
|
+
// Only send identity if relay config is set
|
|
161
95
|
if (!relayChannel || !relayAccountId) return;
|
|
162
|
-
const msg =
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
await mesh.sendToPeer(peerId, JSON.stringify(msg));
|
|
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(() => {});
|
|
96
|
+
const msg = JSON.stringify({
|
|
97
|
+
id: crypto.randomUUID(),
|
|
98
|
+
type: "identity",
|
|
99
|
+
from: mesh.getLocalPeerId(),
|
|
100
|
+
payload: JSON.stringify({
|
|
101
|
+
agentId: api.name,
|
|
102
|
+
channel: relayChannel,
|
|
103
|
+
accountId: relayAccountId,
|
|
104
|
+
}),
|
|
105
|
+
timestamp: Date.now(),
|
|
106
|
+
});
|
|
107
|
+
mesh.sendToPeer(peerId, msg).catch(() => {});
|
|
196
108
|
});
|
|
197
109
|
|
|
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.
|
|
110
|
+
// Dedup: skip initial identity announce for known peers
|
|
202
111
|
if (relayChannel && relayAccountId) {
|
|
203
|
-
const identityMsg =
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
`[libp2p-mesh] Initial identity send to ${peerId} failed: ${String(err)}`,
|
|
219
|
-
);
|
|
220
|
-
});
|
|
112
|
+
const identityMsg = JSON.stringify({
|
|
113
|
+
id: crypto.randomUUID(),
|
|
114
|
+
type: "identity",
|
|
115
|
+
from: mesh.getLocalPeerId(),
|
|
116
|
+
payload: JSON.stringify({
|
|
117
|
+
agentId: api.name,
|
|
118
|
+
channel: relayChannel,
|
|
119
|
+
accountId: relayAccountId,
|
|
120
|
+
}),
|
|
121
|
+
timestamp: Date.now(),
|
|
122
|
+
});
|
|
123
|
+
for (const peerId of mesh.getConnectedPeers()) {
|
|
124
|
+
if (peerIdentityMap.hasIdentity(peerId)) {
|
|
125
|
+
api.logger.debug?.(`[libp2p-mesh] Peer ${peerId} already known, skipping initial identity`);
|
|
126
|
+
continue;
|
|
221
127
|
}
|
|
128
|
+
mesh.sendToPeer(peerId, identityMsg).catch(() => {});
|
|
222
129
|
}
|
|
223
130
|
}
|
|
224
131
|
|
|
225
132
|
const identity = mesh.getInstanceIdentity();
|
|
226
|
-
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${
|
|
133
|
+
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${mesh.getLocalPeerId()}`);
|
|
227
134
|
if (identity) {
|
|
228
135
|
api.logger.info?.(`[libp2p-mesh] Instance Identity: ${identity.id}`);
|
|
229
136
|
}
|