libp2p-mesh 2026.5.12

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.
@@ -0,0 +1,6 @@
1
+ import type { MeshConfig } from "./types.js";
2
+ export declare function resolveDiscoveryConfig(config?: MeshConfig): {
3
+ enabled: boolean;
4
+ mechanism: "mdns" | "bootstrap" | "dht";
5
+ bootstrapList: string[];
6
+ };
@@ -0,0 +1,7 @@
1
+ export function resolveDiscoveryConfig(config) {
2
+ return {
3
+ enabled: true,
4
+ mechanism: config?.discovery ?? "mdns",
5
+ bootstrapList: config?.bootstrapList ?? [],
6
+ };
7
+ }
@@ -0,0 +1,11 @@
1
+ import type { P2PMessage } from "./types.js";
2
+ export type InboundHandlerDeps = {
3
+ logger?: {
4
+ info?: (msg: string) => void;
5
+ debug?: (msg: string) => void;
6
+ warn?: (msg: string) => void;
7
+ error?: (msg: string) => void;
8
+ };
9
+ sendToChannel?: (channelId: string, target: string, text: string) => Promise<void>;
10
+ };
11
+ export declare function handleP2PInbound(msg: P2PMessage, deps: InboundHandlerDeps): void;
@@ -0,0 +1,16 @@
1
+ export function handleP2PInbound(msg, deps) {
2
+ const { logger, sendToChannel } = deps;
3
+ if (msg.type === "broadcast") {
4
+ logger?.info?.(`[libp2p-mesh] Broadcast from ${msg.from} on topic ${msg.topic ?? "(none)"}: ${msg.payload}`);
5
+ return;
6
+ }
7
+ // Direct message — log and forward to local channel
8
+ logger?.info?.(`[libp2p-mesh] Direct message from ${msg.from}: ${msg.payload}`);
9
+ if (!sendToChannel || !msg.payload) {
10
+ return;
11
+ }
12
+ const text = `[来自 ${msg.from}]\n${msg.payload}`;
13
+ sendToChannel("libp2p-mesh", msg.from, text).catch((err) => {
14
+ logger?.error?.(`[libp2p-mesh] Failed to forward direct message from ${msg.from}: ${err}`);
15
+ });
16
+ }
@@ -0,0 +1,10 @@
1
+ import type { MeshConfig, MeshNetwork } from "./types.js";
2
+ export declare function createMeshNetwork(options: {
3
+ config?: MeshConfig;
4
+ logger?: {
5
+ info?: (msg: string) => void;
6
+ debug?: (msg: string) => void;
7
+ warn?: (msg: string) => void;
8
+ error?: (msg: string) => void;
9
+ };
10
+ }): MeshNetwork;
@@ -0,0 +1,305 @@
1
+ // Polyfill for Node.js < 22 (libp2p dependencies use Promise.withResolvers)
2
+ if (!Promise.withResolvers) {
3
+ Promise.withResolvers = function () {
4
+ let resolve;
5
+ let reject;
6
+ const promise = new Promise((res, rej) => {
7
+ resolve = res;
8
+ reject = rej;
9
+ });
10
+ return { promise, resolve: resolve, reject: reject };
11
+ };
12
+ }
13
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
14
+ import { homedir } from "node:os";
15
+ import path from "node:path";
16
+ import { mdns } from "@libp2p/mdns";
17
+ import { mplex } from "@libp2p/mplex";
18
+ import { noise } from "@libp2p/noise";
19
+ import { createEd25519PeerId, createFromProtobuf, exportToProtobuf, } from "@libp2p/peer-id-factory";
20
+ import { tcp } from "@libp2p/tcp";
21
+ import { webSockets } from "@libp2p/websockets";
22
+ import { bootstrap } from "@libp2p/bootstrap";
23
+ import { encode, decode } from "it-length-prefixed";
24
+ import { pipe } from "it-pipe";
25
+ import { createLibp2p } from "libp2p";
26
+ import { Uint8ArrayList } from "uint8arraylist";
27
+ const PROTOCOL = "/openclaw-msg/1.0.0";
28
+ const MAX_SEEN_MESSAGES = 1000;
29
+ function resolvePeerIdPath(customPath) {
30
+ if (customPath)
31
+ return customPath;
32
+ const stateDir = process.env.OPENCLAW_STATE_DIR;
33
+ if (stateDir) {
34
+ return path.join(stateDir, "libp2p", "peer-id.json");
35
+ }
36
+ return path.join(homedir(), ".openclaw", "libp2p", "peer-id.json");
37
+ }
38
+ async function loadOrCreatePeerId(customPath) {
39
+ const peerIdPath = resolvePeerIdPath(customPath);
40
+ try {
41
+ const saved = JSON.parse(await readFile(peerIdPath, "utf8"));
42
+ const peerId = await createFromProtobuf(Buffer.from(saved.protobuf, "base64"));
43
+ return peerId;
44
+ }
45
+ catch {
46
+ const peerId = await createEd25519PeerId();
47
+ const protobuf = Buffer.from(exportToProtobuf(peerId)).toString("base64");
48
+ await mkdir(path.dirname(peerIdPath), { recursive: true });
49
+ await writeFile(peerIdPath, JSON.stringify({ protobuf }, null, 2));
50
+ return peerId;
51
+ }
52
+ }
53
+ export function createMeshNetwork(options) {
54
+ const config = options.config ?? {};
55
+ const logger = options.logger;
56
+ // Use an object property instead of a bare `let` so all closures share
57
+ // the same mutable reference even if the bundler rewrites scopes.
58
+ const state = {
59
+ node: null,
60
+ };
61
+ const seenMessages = new Set();
62
+ const messageHandlers = new Set();
63
+ const topicHandlers = new Map();
64
+ async function start() {
65
+ const peerId = await loadOrCreatePeerId(config.peerIdPath);
66
+ // Build transports dynamically
67
+ const transports = [tcp()];
68
+ if (config.enableWebSocket) {
69
+ transports.push(webSockets());
70
+ }
71
+ // Build peer discovery dynamically
72
+ const peerDiscovery = [];
73
+ const discoveryMechanism = config.discovery ?? "mdns";
74
+ if (discoveryMechanism === "mdns") {
75
+ peerDiscovery.push(mdns({ interval: 1000 }));
76
+ logger?.info?.("[libp2p-mesh] Using mDNS discovery (LAN)");
77
+ }
78
+ else if (discoveryMechanism === "bootstrap") {
79
+ const bootstrapList = config.bootstrapList ?? [];
80
+ if (bootstrapList.length > 0) {
81
+ peerDiscovery.push(bootstrap({ list: bootstrapList }));
82
+ logger?.info?.(`[libp2p-mesh] Using bootstrap discovery (${bootstrapList.length} node(s))`);
83
+ }
84
+ else {
85
+ logger?.warn?.("[libp2p-mesh] discovery=bootstrap but bootstrapList is empty; falling back to mDNS");
86
+ peerDiscovery.push(mdns({ interval: 1000 }));
87
+ }
88
+ }
89
+ else if (discoveryMechanism === "dht") {
90
+ logger?.warn?.("[libp2p-mesh] DHT discovery is not yet implemented; falling back to mDNS");
91
+ peerDiscovery.push(mdns({ interval: 1000 }));
92
+ }
93
+ state.node = await createLibp2p({
94
+ peerId,
95
+ start: false,
96
+ transports,
97
+ connectionEncryption: [noise()],
98
+ streamMuxers: [mplex()],
99
+ addresses: {
100
+ listen: config.listenAddrs ?? ["/ip4/0.0.0.0/tcp/0"],
101
+ },
102
+ peerDiscovery,
103
+ });
104
+ state.node.addEventListener("peer:connect", (evt) => {
105
+ const peerIdStr = evt.detail.toString();
106
+ logger?.debug?.(`[libp2p-mesh] Peer connected: ${peerIdStr}`);
107
+ });
108
+ state.node.addEventListener("peer:disconnect", (evt) => {
109
+ const peerIdStr = evt.detail.toString();
110
+ logger?.debug?.(`[libp2p-mesh] Peer disconnected: ${peerIdStr}`);
111
+ });
112
+ await state.node.handle(PROTOCOL, async ({ stream, connection }) => {
113
+ try {
114
+ await pipe(stream.source, decode, async (source) => {
115
+ for await (const msg of source) {
116
+ const data = new TextDecoder().decode(msg.subarray());
117
+ let parsed;
118
+ try {
119
+ parsed = JSON.parse(data);
120
+ }
121
+ catch {
122
+ logger?.warn?.(`[libp2p-mesh] Failed to parse message from ${connection.remotePeer.toString()}`);
123
+ continue;
124
+ }
125
+ if (seenMessages.has(parsed.id)) {
126
+ continue;
127
+ }
128
+ if (seenMessages.size >= MAX_SEEN_MESSAGES) {
129
+ seenMessages.clear();
130
+ }
131
+ seenMessages.add(parsed.id);
132
+ // Enrich with local timestamp if missing
133
+ if (!parsed.timestamp) {
134
+ parsed.timestamp = Date.now();
135
+ }
136
+ logger?.debug?.(`[libp2p-mesh] Received ${parsed.type} from ${parsed.from}`);
137
+ // Notify direct message handlers
138
+ for (const handler of messageHandlers) {
139
+ try {
140
+ handler(parsed);
141
+ }
142
+ catch (err) {
143
+ logger?.error?.(`[libp2p-mesh] Message handler error: ${String(err)}`);
144
+ }
145
+ }
146
+ // Handle broadcast / topic subscription
147
+ if (parsed.type === "broadcast" && parsed.topic) {
148
+ const handlers = topicHandlers.get(parsed.topic);
149
+ if (handlers) {
150
+ for (const h of handlers) {
151
+ try {
152
+ h(parsed.payload);
153
+ }
154
+ catch (err) {
155
+ logger?.error?.(`[libp2p-mesh] Topic handler error: ${String(err)}`);
156
+ }
157
+ }
158
+ }
159
+ // Flood-fill forward to other connected peers (with TTL guard)
160
+ await forwardBroadcast(parsed, connection.remotePeer.toString());
161
+ }
162
+ }
163
+ });
164
+ }
165
+ catch (err) {
166
+ logger?.error?.(`[libp2p-mesh] Protocol handler error: ${String(err)}`);
167
+ }
168
+ });
169
+ await state.node.start();
170
+ logger?.info?.(`[libp2p-mesh] Node started. Peer ID: ${state.node.peerId.toString()}`);
171
+ logger?.info?.(`[libp2p-mesh] Listening on: ${state.node.getMultiaddrs().map((ma) => ma.toString()).join(", ")}`);
172
+ }
173
+ async function stop() {
174
+ if (state.node) {
175
+ await state.node.stop();
176
+ state.node = null;
177
+ logger?.info?.("[libp2p-mesh] Node stopped.");
178
+ }
179
+ }
180
+ async function sendToPeer(peerId, message) {
181
+ if (!state.node) {
182
+ throw new Error("Mesh network is not started");
183
+ }
184
+ const msg = {
185
+ id: crypto.randomUUID(),
186
+ type: "direct",
187
+ from: state.node.peerId.toString(),
188
+ to: peerId,
189
+ payload: message,
190
+ timestamp: Date.now(),
191
+ };
192
+ const data = new TextEncoder().encode(JSON.stringify(msg));
193
+ const abortController = new AbortController();
194
+ const timeout = setTimeout(() => abortController.abort(), 8000);
195
+ try {
196
+ const { peerIdFromString } = await import("@libp2p/peer-id");
197
+ logger?.debug?.(`[libp2p-mesh] dialProtocol to ${peerId}`);
198
+ const stream = await state.node.dialProtocol(peerIdFromString(peerId), PROTOCOL, {
199
+ signal: abortController.signal,
200
+ });
201
+ if (!stream) {
202
+ throw new Error(`Failed to establish stream to ${peerId}; peer may be unreachable`);
203
+ }
204
+ logger?.debug?.(`[libp2p-mesh] stream opened to ${peerId}`);
205
+ await pipe([new Uint8ArrayList(data)], encode, stream.sink);
206
+ logger?.debug?.(`[libp2p-mesh] message sent to ${peerId}`);
207
+ }
208
+ catch (err) {
209
+ logger?.error?.(`[libp2p-mesh] sendToPeer error: ${String(err)}`);
210
+ if (abortController.signal.aborted) {
211
+ throw new Error(`Send to ${peerId} timed out after 8s`);
212
+ }
213
+ throw err;
214
+ }
215
+ finally {
216
+ clearTimeout(timeout);
217
+ }
218
+ }
219
+ async function publishToTopic(topic, message) {
220
+ if (!state.node) {
221
+ throw new Error("Mesh network is not started");
222
+ }
223
+ const msg = {
224
+ id: crypto.randomUUID(),
225
+ type: "broadcast",
226
+ from: state.node.peerId.toString(),
227
+ topic,
228
+ payload: message,
229
+ timestamp: Date.now(),
230
+ };
231
+ const data = new TextEncoder().encode(JSON.stringify(msg));
232
+ const connections = state.node.getConnections();
233
+ let sent = 0;
234
+ for (const conn of connections) {
235
+ const abortController = new AbortController();
236
+ const timeout = setTimeout(() => abortController.abort(), 5000);
237
+ try {
238
+ const stream = await conn.newStream(PROTOCOL, { signal: abortController.signal });
239
+ await pipe([new Uint8ArrayList(data)], encode, stream.sink);
240
+ sent++;
241
+ }
242
+ catch {
243
+ // Ignore individual forwarding errors
244
+ }
245
+ finally {
246
+ clearTimeout(timeout);
247
+ }
248
+ }
249
+ logger?.debug?.(`[libp2p-mesh] Broadcast sent to ${sent} peer(s) on topic ${topic}`);
250
+ }
251
+ async function forwardBroadcast(msg, fromPeerId) {
252
+ if (!state.node)
253
+ return;
254
+ // Simple flood-fill: forward to all connected peers except the sender
255
+ const data = new TextEncoder().encode(JSON.stringify(msg));
256
+ for (const conn of state.node.getConnections()) {
257
+ const remotePeerId = conn.remotePeer.toString();
258
+ if (remotePeerId === fromPeerId)
259
+ continue;
260
+ const abortController = new AbortController();
261
+ const timeout = setTimeout(() => abortController.abort(), 5000);
262
+ try {
263
+ const stream = await conn.newStream(PROTOCOL, { signal: abortController.signal });
264
+ await pipe([new Uint8ArrayList(data)], encode, stream.sink);
265
+ }
266
+ catch {
267
+ // Ignore forwarding errors
268
+ }
269
+ finally {
270
+ clearTimeout(timeout);
271
+ }
272
+ }
273
+ }
274
+ function onMessage(handler) {
275
+ messageHandlers.add(handler);
276
+ return () => {
277
+ messageHandlers.delete(handler);
278
+ };
279
+ }
280
+ async function subscribeToTopic(topic, handler) {
281
+ if (!topicHandlers.has(topic)) {
282
+ topicHandlers.set(topic, new Set());
283
+ }
284
+ topicHandlers.get(topic).add(handler);
285
+ }
286
+ function getLocalPeerId() {
287
+ return state.node?.peerId.toString() ?? "";
288
+ }
289
+ function getConnectedPeers() {
290
+ if (!state.node)
291
+ return [];
292
+ const peers = state.node.getConnections().map((c) => c.remotePeer.toString());
293
+ return [...new Set(peers)];
294
+ }
295
+ return {
296
+ start,
297
+ stop,
298
+ sendToPeer,
299
+ onMessage,
300
+ publishToTopic,
301
+ subscribeToTopic,
302
+ getLocalPeerId,
303
+ getConnectedPeers,
304
+ };
305
+ }
@@ -0,0 +1,2 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
2
+ export declare function registerLibp2pMesh(api: OpenClawPluginApi): void;
@@ -0,0 +1,84 @@
1
+ import { createLibp2pMeshChannel } from "./channel.js";
2
+ import { handleP2PInbound } from "./inbound.js";
3
+ import { createMeshNetwork } from "./mesh.js";
4
+ import { buildP2PTools } from "./agent-tools.js";
5
+ import { sendViaMesh } from "./send.js";
6
+ export function registerLibp2pMesh(api) {
7
+ const mesh = createMeshNetwork({
8
+ config: api.pluginConfig,
9
+ logger: api.logger,
10
+ });
11
+ const channel = createLibp2pMeshChannel(mesh);
12
+ // 1. Register Service (manages libp2p node lifecycle)
13
+ api.registerService({
14
+ id: "libp2p-mesh",
15
+ start: async () => {
16
+ api.logger.info?.("[libp2p-mesh] >>> Service start called");
17
+ await mesh.start();
18
+ api.logger.info?.(`[libp2p-mesh] >>> mesh.start() done, Peer ID: ${mesh.getLocalPeerId()}`);
19
+ mesh.onMessage((msg) => {
20
+ api.logger.info?.("[libp2p-mesh] >>> onMessage handler registered");
21
+ const sendToChannel = async (_channelId, target, text) => {
22
+ try {
23
+ await sendViaMesh(mesh, target, text);
24
+ }
25
+ catch (err) {
26
+ api.logger.error?.(`[libp2p-mesh] sendToChannel error: ${err}`);
27
+ }
28
+ };
29
+ handleP2PInbound(msg, { logger: api.logger, sendToChannel });
30
+ });
31
+ api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${mesh.getLocalPeerId()}`);
32
+ },
33
+ stop: async () => {
34
+ await mesh.stop();
35
+ api.logger.info?.("[libp2p-mesh] Service stopped.");
36
+ },
37
+ });
38
+ // 2. Register Channel (lightweight debugging surface)
39
+ api.registerChannel({
40
+ plugin: channel,
41
+ });
42
+ // 3. Register Agent Tools
43
+ const tools = buildP2PTools(mesh);
44
+ for (const tool of tools) {
45
+ api.registerTool(tool);
46
+ }
47
+ // 4. Register Command (injects agent prompt guidance so the LLM knows to use
48
+ // p2p_list_peers / p2p_send_message / p2p_broadcast for P2P mesh operations
49
+ // instead of the built-in `nodes` tool, which only manages OpenClaw paired nodes.)
50
+ api.registerCommand({
51
+ name: "p2p",
52
+ description: "P2P mesh network operations (list peers, send messages, broadcast).",
53
+ agentPromptGuidance: [
54
+ "When the user asks to list, show, or discover P2P mesh peers or connected agents, use the p2p_list_peers tool. Do NOT use the built-in `nodes` tool for this — `nodes` lists OpenClaw paired nodes, not P2P mesh peers.",
55
+ "When the user asks to send a direct message to another peer/agent, use p2p_send_message.",
56
+ "When the user asks to broadcast a message to all peers on a topic, use p2p_broadcast.",
57
+ ],
58
+ handler: (_ctx) => {
59
+ return { text: "P2P mesh tools are available. Use p2p_list_peers, p2p_send_message, or p2p_broadcast." };
60
+ },
61
+ });
62
+ // 5. Register CLI commands for operator access
63
+ api.registerCli((ctx) => {
64
+ ctx.program
65
+ .command("status")
66
+ .description("Show local peer ID and connected peers")
67
+ .action(async () => {
68
+ const peers = mesh.getConnectedPeers();
69
+ ctx.logger.info?.(`Local Peer ID: ${mesh.getLocalPeerId()}\nConnected peers (${peers.length}): ${peers.join(", ") || "none"}`);
70
+ });
71
+ ctx.program
72
+ .command("peers")
73
+ .description("Alias for mesh status — list connected peers")
74
+ .action(async () => {
75
+ const peers = mesh.getConnectedPeers();
76
+ ctx.logger.info?.(`Connected peers (${peers.length}): ${peers.join(", ") || "none"}`);
77
+ });
78
+ }, { parentPath: ["p2p"] });
79
+ // 6. Register Hook (log received messages for observability)
80
+ api.registerHook("message:received", async (event) => {
81
+ const ctx = event.context;
82
+ api.logger.debug?.(`[libp2p-mesh] message received on channel ${ctx?.channelId ?? "unknown"}`);
83
+ }, { name: "libp2p-mesh-message-received" });
84
+ }
@@ -0,0 +1,3 @@
1
+ import type { MeshNetwork } from "./types.js";
2
+ export declare function broadcastToMesh(mesh: MeshNetwork, topic: string, message: string): Promise<void>;
3
+ export declare function subscribeToMeshTopic(mesh: MeshNetwork, topic: string, handler: (msg: string) => void): Promise<() => void>;
@@ -0,0 +1,10 @@
1
+ export async function broadcastToMesh(mesh, topic, message) {
2
+ await mesh.publishToTopic(topic, message);
3
+ }
4
+ export async function subscribeToMeshTopic(mesh, topic, handler) {
5
+ await mesh.subscribeToTopic(topic, handler);
6
+ return () => {
7
+ // Unsubscribe is not directly supported in the current MeshNetwork interface;
8
+ // the handler reference could be stored externally for future cleanup.
9
+ };
10
+ }
@@ -0,0 +1,2 @@
1
+ import type { MeshNetwork } from "./types.js";
2
+ export declare function sendViaMesh(mesh: MeshNetwork, peerId: string, text: string): Promise<void>;
@@ -0,0 +1,9 @@
1
+ export async function sendViaMesh(mesh, peerId, text) {
2
+ if (!peerId || !peerId.trim()) {
3
+ throw new Error("Peer ID is required");
4
+ }
5
+ if (!text || !text.trim()) {
6
+ throw new Error("Message text is required");
7
+ }
8
+ await mesh.sendToPeer(peerId.trim(), text.trim());
9
+ }
@@ -0,0 +1,33 @@
1
+ export interface P2PMessage {
2
+ id: string;
3
+ type: "direct" | "broadcast" | "agent-sync";
4
+ from: string;
5
+ to?: string;
6
+ topic?: string;
7
+ payload: string;
8
+ timestamp: number;
9
+ }
10
+ export interface MeshConfig {
11
+ listenAddrs?: string[];
12
+ discovery?: "mdns" | "bootstrap" | "dht";
13
+ bootstrapList?: string[];
14
+ meshTopic?: string;
15
+ enableAgentSync?: boolean;
16
+ enableWebSocket?: boolean;
17
+ peerIdPath?: string;
18
+ }
19
+ export interface MeshNetwork {
20
+ start(): Promise<void>;
21
+ stop(): Promise<void>;
22
+ sendToPeer(peerId: string, message: string): Promise<void>;
23
+ onMessage(handler: (msg: P2PMessage) => void): () => void;
24
+ publishToTopic(topic: string, message: string): Promise<void>;
25
+ subscribeToTopic(topic: string, handler: (msg: string) => void): Promise<void>;
26
+ getLocalPeerId(): string;
27
+ getConnectedPeers(): string[];
28
+ }
29
+ export type MeshAccount = {
30
+ accountId: string;
31
+ configured: boolean;
32
+ enabled: boolean;
33
+ };
@@ -0,0 +1 @@
1
+ export {};
package/index.ts ADDED
@@ -0,0 +1,65 @@
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/core";
2
+ import type { OpenClawPluginConfigSchema } from "openclaw/plugin-sdk/core";
3
+ import { registerLibp2pMesh } from "./src/plugin.js";
4
+
5
+ function createLibp2pMeshConfigSchema(): OpenClawPluginConfigSchema {
6
+ return {
7
+ safeParse(value: unknown) {
8
+ if (value === undefined) {
9
+ return { success: true, data: undefined };
10
+ }
11
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
12
+ return {
13
+ success: false,
14
+ error: { issues: [{ path: [], message: "expected config object" }] },
15
+ };
16
+ }
17
+ return { success: true, data: value };
18
+ },
19
+ jsonSchema: {
20
+ type: "object",
21
+ additionalProperties: false,
22
+ properties: {
23
+ listenAddrs: {
24
+ type: "array",
25
+ items: { type: "string" },
26
+ default: ["/ip4/0.0.0.0/tcp/0"],
27
+ },
28
+ enableWebSocket: {
29
+ type: "boolean",
30
+ default: false,
31
+ description: "Enable WebSocket transport (useful for browser compatibility)",
32
+ },
33
+ discovery: {
34
+ type: "string",
35
+ enum: ["mdns", "bootstrap", "dht"],
36
+ default: "mdns",
37
+ },
38
+ bootstrapList: {
39
+ type: "array",
40
+ items: { type: "string" },
41
+ },
42
+ meshTopic: {
43
+ type: "string",
44
+ default: "openclaw-mesh",
45
+ },
46
+ enablePubsub: {
47
+ type: "boolean",
48
+ default: true,
49
+ },
50
+ enableAgentSync: {
51
+ type: "boolean",
52
+ default: true,
53
+ },
54
+ },
55
+ },
56
+ };
57
+ }
58
+
59
+ export default definePluginEntry({
60
+ id: "libp2p-mesh",
61
+ name: "libp2p Mesh Network",
62
+ description: "P2P network for cross-instance agent communication via libp2p.",
63
+ configSchema: createLibp2pMeshConfigSchema(),
64
+ register: registerLibp2pMesh,
65
+ });
@@ -0,0 +1,97 @@
1
+ {
2
+ "id": "libp2p-mesh",
3
+ "name": "libp2p Mesh Network",
4
+ "description": "P2P network for cross-instance agent communication via libp2p.",
5
+ "configSchema": {
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "properties": {
9
+ "listenAddrs": {
10
+ "type": "array",
11
+ "items": { "type": "string" },
12
+ "default": ["/ip4/0.0.0.0/tcp/0"]
13
+ },
14
+ "enableWebSocket": {
15
+ "type": "boolean",
16
+ "default": false,
17
+ "description": "Enable WebSocket transport (useful for browser compatibility)"
18
+ },
19
+ "discovery": {
20
+ "type": "string",
21
+ "enum": ["mdns", "bootstrap", "dht"],
22
+ "default": "mdns"
23
+ },
24
+ "bootstrapList": {
25
+ "type": "array",
26
+ "items": { "type": "string" }
27
+ },
28
+ "meshTopic": {
29
+ "type": "string",
30
+ "default": "openclaw-mesh"
31
+ },
32
+ "enablePubsub": {
33
+ "type": "boolean",
34
+ "default": true
35
+ },
36
+ "enableAgentSync": {
37
+ "type": "boolean",
38
+ "default": true
39
+ }
40
+ }
41
+ },
42
+ "channels": ["libp2p-mesh"],
43
+ "channelConfigs": {
44
+ "libp2p-mesh": {
45
+ "schema": {
46
+ "type": "object",
47
+ "additionalProperties": false,
48
+ "properties": {
49
+ "listenAddrs": {
50
+ "type": "array",
51
+ "items": { "type": "string" },
52
+ "default": ["/ip4/0.0.0.0/tcp/0"]
53
+ },
54
+ "enableWebSocket": {
55
+ "type": "boolean",
56
+ "default": false,
57
+ "description": "Enable WebSocket transport"
58
+ },
59
+ "discovery": {
60
+ "type": "string",
61
+ "enum": ["mdns", "bootstrap", "dht"],
62
+ "default": "mdns"
63
+ },
64
+ "bootstrapList": {
65
+ "type": "array",
66
+ "items": { "type": "string" }
67
+ },
68
+ "meshTopic": {
69
+ "type": "string",
70
+ "default": "openclaw-mesh"
71
+ }
72
+ }
73
+ }
74
+ }
75
+ },
76
+ "uiHints": {
77
+ "listenAddrs": {
78
+ "label": "Listen Addresses",
79
+ "help": "libp2p listen multiaddrs (e.g. /ip4/0.0.0.0/tcp/0).",
80
+ "advanced": true
81
+ },
82
+ "discovery": {
83
+ "label": "Discovery Mechanism",
84
+ "help": "Peer discovery: mdns (LAN), bootstrap (static list), or dht (Kademlia)."
85
+ },
86
+ "bootstrapList": {
87
+ "label": "Bootstrap Peers",
88
+ "help": "Static bootstrap peer multiaddrs when discovery=bootstrap.",
89
+ "advanced": true
90
+ },
91
+ "meshTopic": {
92
+ "label": "Mesh Topic",
93
+ "help": "Default topic used for P2P broadcasts.",
94
+ "advanced": true
95
+ }
96
+ }
97
+ }