libp2p-mesh 2026.5.24 → 2026.5.26
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 +34 -32
- package/dist/src/plugin.js +48 -0
- package/package.json +1 -1
- package/src/inbound.ts +32 -30
- package/src/plugin.ts +57 -0
package/dist/src/inbound.js
CHANGED
|
@@ -60,7 +60,8 @@ export async function handleP2PInbound(msg, deps) {
|
|
|
60
60
|
else if (msg.type === "direct") {
|
|
61
61
|
// onPeerConnect 通过 sendToPeer 发送 identity 时会被包装成
|
|
62
62
|
// type=direct 的外壳,真正的 identity 消息藏在 payload 里。
|
|
63
|
-
//
|
|
63
|
+
// 先检测并处理:注册对方身份,但继续往下执行让底部日志打印。
|
|
64
|
+
let isIdentity = false;
|
|
64
65
|
try {
|
|
65
66
|
const raw = JSON.parse(msg.payload);
|
|
66
67
|
if (raw && raw.type === "identity") {
|
|
@@ -74,48 +75,49 @@ export async function handleP2PInbound(msg, deps) {
|
|
|
74
75
|
send: async () => { }, // no-op; 双方都通过 onPeerConnect 主动发送身份
|
|
75
76
|
logger,
|
|
76
77
|
});
|
|
77
|
-
|
|
78
|
-
return;
|
|
78
|
+
isIdentity = true;
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
catch {
|
|
82
82
|
// payload 不是 JSON,正常走 direct 消息逻辑
|
|
83
83
|
}
|
|
84
|
-
if (!
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
84
|
+
if (!isIdentity) {
|
|
85
|
+
if (!msg.to) {
|
|
86
|
+
logger?.warn?.("[libp2p-mesh] Direct message missing 'to' field");
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
const receiver = deps.peerIdentityMap.resolve(msg.to);
|
|
90
|
+
if (!receiver) {
|
|
91
|
+
// Fallback: check if the recipient is our own local identity.
|
|
92
|
+
// setLocalIdentity stores the local peer separately (not in peers
|
|
93
|
+
// Map) to avoid duplication, so direct messages addressed to
|
|
94
|
+
// ourselves need this fallback to resolve correctly.
|
|
95
|
+
const localPeerId = deps.peerIdentityMap.getLocalPeerId();
|
|
96
|
+
const localId = deps.peerIdentityMap.getLocalIdentity();
|
|
97
|
+
if (localId && msg.to === localPeerId) {
|
|
98
|
+
const sessionKey = buildAgentSessionKey({
|
|
99
|
+
agentId: localId.agentId,
|
|
100
|
+
channel: localId.channel,
|
|
101
|
+
accountId: localId.accountId,
|
|
102
|
+
});
|
|
103
|
+
const text = `【来自P2P网络的消息】\n来源:${msg.from}\n内容:${msg.payload}\n\n` +
|
|
104
|
+
`请你通过你当前的 channel 将此消息转发给你所服务的用户。转发完成后,请务必调用 p2p_send_message 工具向 ${msg.from} 回复送达确认。`;
|
|
105
|
+
deps.enqueueNextTurnInjection({ sessionKey, text, placement: "prepend_context" });
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
logger?.warn?.(`[libp2p-mesh] Unknown recipient peer: ${msg.to}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
97
112
|
const sessionKey = buildAgentSessionKey({
|
|
98
|
-
agentId:
|
|
99
|
-
channel:
|
|
100
|
-
accountId:
|
|
113
|
+
agentId: receiver.agentId,
|
|
114
|
+
channel: receiver.channel,
|
|
115
|
+
accountId: receiver.accountId,
|
|
101
116
|
});
|
|
102
117
|
const text = `【来自P2P网络的消息】\n来源:${msg.from}\n内容:${msg.payload}\n\n` +
|
|
103
118
|
`请你通过你当前的 channel 将此消息转发给你所服务的用户。转发完成后,请务必调用 p2p_send_message 工具向 ${msg.from} 回复送达确认。`;
|
|
104
119
|
deps.enqueueNextTurnInjection({ sessionKey, text, placement: "prepend_context" });
|
|
105
120
|
}
|
|
106
|
-
else {
|
|
107
|
-
logger?.warn?.(`[libp2p-mesh] Unknown recipient peer: ${msg.to}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
const sessionKey = buildAgentSessionKey({
|
|
112
|
-
agentId: receiver.agentId,
|
|
113
|
-
channel: receiver.channel,
|
|
114
|
-
accountId: receiver.accountId,
|
|
115
|
-
});
|
|
116
|
-
const text = `【来自P2P网络的消息】\n来源:${msg.from}\n内容:${msg.payload}\n\n` +
|
|
117
|
-
`请你通过你当前的 channel 将此消息转发给你所服务的用户。转发完成后,请务必调用 p2p_send_message 工具向 ${msg.from} 回复送达确认。`;
|
|
118
|
-
deps.enqueueNextTurnInjection({ sessionKey, text, placement: "prepend_context" });
|
|
119
121
|
}
|
|
120
122
|
}
|
|
121
123
|
// If the sender is not yet registered, register them synchronously
|
package/dist/src/plugin.js
CHANGED
|
@@ -50,6 +50,38 @@ export function registerLibp2pMesh(api) {
|
|
|
50
50
|
api.logger.error?.(`[libp2p-mesh] Identity exchange error: ${String(err)}`);
|
|
51
51
|
});
|
|
52
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
|
+
}
|
|
53
85
|
else {
|
|
54
86
|
handleP2PInbound(msg, buildInboundDeps());
|
|
55
87
|
}
|
|
@@ -132,6 +164,22 @@ export function registerLibp2pMesh(api) {
|
|
|
132
164
|
peerIdentityMap.unregister(peerId);
|
|
133
165
|
peerIdentityMap.saveNow().catch(() => { });
|
|
134
166
|
});
|
|
167
|
+
// Actively announce identity to all currently-connected peers.
|
|
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.
|
|
171
|
+
if (relayChannel && relayAccountId) {
|
|
172
|
+
const identityMsg = buildIdentityMessage(localPeerId ?? "", api.name, relayChannel, relayAccountId, localInstanceId);
|
|
173
|
+
const connectedNow = mesh.getConnectedPeers();
|
|
174
|
+
if (connectedNow.length > 0) {
|
|
175
|
+
api.logger.info?.(`[libp2p-mesh] Sending initial identity to ${connectedNow.length} connected peer(s)`);
|
|
176
|
+
for (const peerId of connectedNow) {
|
|
177
|
+
mesh.sendToPeer(peerId, JSON.stringify(identityMsg)).catch((err) => {
|
|
178
|
+
api.logger.debug?.(`[libp2p-mesh] Initial identity send to ${peerId} failed: ${String(err)}`);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
135
183
|
const identity = mesh.getInstanceIdentity();
|
|
136
184
|
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${localPeerId}`);
|
|
137
185
|
if (identity) {
|
package/package.json
CHANGED
package/src/inbound.ts
CHANGED
|
@@ -77,7 +77,8 @@ export async function handleP2PInbound(msg: P2PMessage, deps?: InboundHandlerDep
|
|
|
77
77
|
} else if (msg.type === "direct") {
|
|
78
78
|
// onPeerConnect 通过 sendToPeer 发送 identity 时会被包装成
|
|
79
79
|
// type=direct 的外壳,真正的 identity 消息藏在 payload 里。
|
|
80
|
-
//
|
|
80
|
+
// 先检测并处理:注册对方身份,但继续往下执行让底部日志打印。
|
|
81
|
+
let isIdentity = false;
|
|
81
82
|
try {
|
|
82
83
|
const raw = JSON.parse(msg.payload);
|
|
83
84
|
if (raw && raw.type === "identity") {
|
|
@@ -91,47 +92,48 @@ export async function handleP2PInbound(msg: P2PMessage, deps?: InboundHandlerDep
|
|
|
91
92
|
send: async () => {}, // no-op; 双方都通过 onPeerConnect 主动发送身份
|
|
92
93
|
logger,
|
|
93
94
|
});
|
|
94
|
-
|
|
95
|
-
return;
|
|
95
|
+
isIdentity = true;
|
|
96
96
|
}
|
|
97
97
|
} catch {
|
|
98
98
|
// payload 不是 JSON,正常走 direct 消息逻辑
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
if (!
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
101
|
+
if (!isIdentity) {
|
|
102
|
+
if (!msg.to) {
|
|
103
|
+
logger?.warn?.("[libp2p-mesh] Direct message missing 'to' field");
|
|
104
|
+
} else {
|
|
105
|
+
const receiver = deps.peerIdentityMap.resolve(msg.to);
|
|
106
|
+
if (!receiver) {
|
|
107
|
+
// Fallback: check if the recipient is our own local identity.
|
|
108
|
+
// setLocalIdentity stores the local peer separately (not in peers
|
|
109
|
+
// Map) to avoid duplication, so direct messages addressed to
|
|
110
|
+
// ourselves need this fallback to resolve correctly.
|
|
111
|
+
const localPeerId = deps.peerIdentityMap.getLocalPeerId();
|
|
112
|
+
const localId = deps.peerIdentityMap.getLocalIdentity();
|
|
113
|
+
if (localId && msg.to === localPeerId) {
|
|
114
|
+
const sessionKey = buildAgentSessionKey({
|
|
115
|
+
agentId: localId.agentId,
|
|
116
|
+
channel: localId.channel,
|
|
117
|
+
accountId: localId.accountId,
|
|
118
|
+
});
|
|
119
|
+
const text =
|
|
120
|
+
`【来自P2P网络的消息】\n来源:${msg.from}\n内容:${msg.payload}\n\n` +
|
|
121
|
+
`请你通过你当前的 channel 将此消息转发给你所服务的用户。转发完成后,请务必调用 p2p_send_message 工具向 ${msg.from} 回复送达确认。`;
|
|
122
|
+
deps.enqueueNextTurnInjection({ sessionKey, text, placement: "prepend_context" });
|
|
123
|
+
} else {
|
|
124
|
+
logger?.warn?.(`[libp2p-mesh] Unknown recipient peer: ${msg.to}`);
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
113
127
|
const sessionKey = buildAgentSessionKey({
|
|
114
|
-
agentId:
|
|
115
|
-
channel:
|
|
116
|
-
accountId:
|
|
128
|
+
agentId: receiver.agentId,
|
|
129
|
+
channel: receiver.channel,
|
|
130
|
+
accountId: receiver.accountId,
|
|
117
131
|
});
|
|
118
132
|
const text =
|
|
119
133
|
`【来自P2P网络的消息】\n来源:${msg.from}\n内容:${msg.payload}\n\n` +
|
|
120
134
|
`请你通过你当前的 channel 将此消息转发给你所服务的用户。转发完成后,请务必调用 p2p_send_message 工具向 ${msg.from} 回复送达确认。`;
|
|
121
135
|
deps.enqueueNextTurnInjection({ sessionKey, text, placement: "prepend_context" });
|
|
122
|
-
} else {
|
|
123
|
-
logger?.warn?.(`[libp2p-mesh] Unknown recipient peer: ${msg.to}`);
|
|
124
136
|
}
|
|
125
|
-
} else {
|
|
126
|
-
const sessionKey = buildAgentSessionKey({
|
|
127
|
-
agentId: receiver.agentId,
|
|
128
|
-
channel: receiver.channel,
|
|
129
|
-
accountId: receiver.accountId,
|
|
130
|
-
});
|
|
131
|
-
const text =
|
|
132
|
-
`【来自P2P网络的消息】\n来源:${msg.from}\n内容:${msg.payload}\n\n` +
|
|
133
|
-
`请你通过你当前的 channel 将此消息转发给你所服务的用户。转发完成后,请务必调用 p2p_send_message 工具向 ${msg.from} 回复送达确认。`;
|
|
134
|
-
deps.enqueueNextTurnInjection({ sessionKey, text, placement: "prepend_context" });
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
139
|
|
package/src/plugin.ts
CHANGED
|
@@ -58,6 +58,36 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
58
58
|
}).catch((err) => {
|
|
59
59
|
api.logger.error?.(`[libp2p-mesh] Identity exchange error: ${String(err)}`);
|
|
60
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
|
+
}
|
|
61
91
|
} else {
|
|
62
92
|
handleP2PInbound(msg, buildInboundDeps());
|
|
63
93
|
}
|
|
@@ -165,6 +195,33 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
165
195
|
peerIdentityMap.saveNow().catch(() => {});
|
|
166
196
|
});
|
|
167
197
|
|
|
198
|
+
// Actively announce identity to all currently-connected peers.
|
|
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.
|
|
202
|
+
if (relayChannel && relayAccountId) {
|
|
203
|
+
const identityMsg = buildIdentityMessage(
|
|
204
|
+
localPeerId ?? "",
|
|
205
|
+
api.name,
|
|
206
|
+
relayChannel,
|
|
207
|
+
relayAccountId,
|
|
208
|
+
localInstanceId,
|
|
209
|
+
);
|
|
210
|
+
const connectedNow = mesh.getConnectedPeers();
|
|
211
|
+
if (connectedNow.length > 0) {
|
|
212
|
+
api.logger.info?.(
|
|
213
|
+
`[libp2p-mesh] Sending initial identity to ${connectedNow.length} connected peer(s)`,
|
|
214
|
+
);
|
|
215
|
+
for (const peerId of connectedNow) {
|
|
216
|
+
mesh.sendToPeer(peerId, JSON.stringify(identityMsg)).catch((err) => {
|
|
217
|
+
api.logger.debug?.(
|
|
218
|
+
`[libp2p-mesh] Initial identity send to ${peerId} failed: ${String(err)}`,
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
168
225
|
const identity = mesh.getInstanceIdentity();
|
|
169
226
|
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${localPeerId}`);
|
|
170
227
|
if (identity) {
|