libp2p-mesh 2026.6.2 → 2026.6.4

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.
@@ -1,83 +1,41 @@
1
- import { spawn } from "node:child_process";
2
- export function createOpenClawCliInboundDelivery(options) {
3
- const command = options?.command ?? "openclaw";
4
- const timeoutMs = options?.timeoutMs ?? 15000;
5
- const logger = options?.logger;
1
+ function summarizeError(error) {
2
+ return error instanceof Error ? error.message : String(error);
3
+ }
4
+ export function createOpenClawRuntimeInboundDelivery(options) {
5
+ const { config, loadAdapter, logger } = options;
6
6
  return {
7
- deliver(request) {
8
- const args = [
9
- "message",
10
- "send",
11
- "--channel",
12
- request.channel,
13
- "--target",
14
- request.target,
15
- "--message",
16
- request.text,
17
- ];
18
- logger?.debug?.(`[libp2p-mesh] Forwarding inbound delivery via CLI: ${command} ${args.join(" ")}`);
19
- return new Promise((resolve) => {
20
- const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"] });
21
- const stdout = [];
22
- const stderr = [];
23
- let settled = false;
24
- let timeout;
25
- const finish = (result) => {
26
- if (settled) {
27
- return;
28
- }
29
- settled = true;
30
- if (timeout) {
31
- clearTimeout(timeout);
32
- }
33
- resolve(result);
7
+ async deliver(request) {
8
+ logger?.debug?.(`[libp2p-mesh] Forwarding inbound delivery via runtime channel adapter: ${request.channel}/${request.target}`);
9
+ const adapter = await loadAdapter(request.channel);
10
+ if (!adapter?.sendText) {
11
+ return {
12
+ ok: false,
13
+ channel: request.channel,
14
+ target: request.target,
15
+ error: `channel ${request.channel} does not expose runtime text delivery`,
34
16
  };
35
- timeout = setTimeout(() => {
36
- logger?.warn?.(`[libp2p-mesh] Inbound delivery command timed out after ${timeoutMs}ms`);
37
- child.kill("SIGTERM");
38
- finish({
39
- ok: false,
40
- channel: request.channel,
41
- target: request.target,
42
- error: `openclaw message send timed out after ${timeoutMs}ms`,
43
- });
44
- }, timeoutMs);
45
- child.stdout.on("data", (chunk) => {
46
- stdout.push(chunk);
47
- });
48
- child.stderr.on("data", (chunk) => {
49
- stderr.push(chunk);
50
- });
51
- child.on("error", (err) => {
52
- finish({
53
- ok: false,
54
- channel: request.channel,
55
- target: request.target,
56
- error: String(err),
57
- });
17
+ }
18
+ try {
19
+ await adapter.sendText({
20
+ cfg: config,
21
+ to: request.target,
22
+ text: request.text,
58
23
  });
59
- child.on("close", (code) => {
60
- if (code === 0) {
61
- logger?.info?.(`[libp2p-mesh] Delivered inbound message to ${request.channel}/${request.target}`);
62
- finish({
63
- ok: true,
64
- channel: request.channel,
65
- target: request.target,
66
- });
67
- return;
68
- }
69
- const stderrText = Buffer.concat(stderr).toString().trim();
70
- const stdoutText = Buffer.concat(stdout).toString().trim();
71
- finish({
72
- ok: false,
73
- channel: request.channel,
74
- target: request.target,
75
- error: stderrText ||
76
- stdoutText ||
77
- `openclaw message send exited with code ${code}`,
78
- });
79
- });
80
- });
24
+ }
25
+ catch (error) {
26
+ return {
27
+ ok: false,
28
+ channel: request.channel,
29
+ target: request.target,
30
+ error: summarizeError(error),
31
+ };
32
+ }
33
+ logger?.info?.(`[libp2p-mesh] Delivered inbound message to ${request.channel}/${request.target}`);
34
+ return {
35
+ ok: true,
36
+ channel: request.channel,
37
+ target: request.target,
38
+ };
81
39
  },
82
40
  };
83
41
  }
@@ -6,5 +6,6 @@ export type InboundHandlerDeps = {
6
6
  warn?: (msg: string) => void;
7
7
  error?: (msg: string) => void;
8
8
  };
9
+ sendToChannel?: (channelId: string, target: string, text: string) => Promise<void>;
9
10
  };
10
11
  export declare function handleP2PInbound(msg: P2PMessage, deps: InboundHandlerDeps): void;
@@ -1,6 +1,6 @@
1
1
  import { verifyInstanceSignature } from "./instance-id.js";
2
2
  export function handleP2PInbound(msg, deps) {
3
- const { logger } = deps;
3
+ const { logger, sendToChannel } = deps;
4
4
  const instanceTag = msg.instanceId ? ` [instance: ${msg.instanceId}]` : "";
5
5
  const signedTag = msg.signature ? " [signed]" : "";
6
6
  // Verify signature if present
@@ -28,8 +28,15 @@ export function handleP2PInbound(msg, deps) {
28
28
  }
29
29
  if (msg.type === "broadcast") {
30
30
  logger?.info?.(`[libp2p-mesh] Broadcast from ${msg.from}${instanceTag}${signedTag} on topic ${msg.topic ?? "(none)"}: ${msg.payload}`);
31
+ return;
31
32
  }
32
- else {
33
- logger?.info?.(`[libp2p-mesh] Direct message from ${msg.from}${instanceTag}${signedTag}: ${msg.payload}`);
33
+ // Direct message — log and forward to local channel
34
+ logger?.info?.(`[libp2p-mesh] Direct message from ${msg.from}${instanceTag}${signedTag}: ${msg.payload}`);
35
+ if (!sendToChannel || !msg.payload) {
36
+ return;
34
37
  }
38
+ const text = `[来自 ${msg.from}]\n${msg.payload}`;
39
+ sendToChannel("libp2p-mesh", msg.from, text).catch((err) => {
40
+ logger?.error?.(`[libp2p-mesh] Failed to forward direct message from ${msg.from}: ${err}`);
41
+ });
35
42
  }
@@ -13,6 +13,67 @@ function isNonEmptyString(value) {
13
13
  function summarizeError(error) {
14
14
  return error instanceof Error ? error.message : String(error);
15
15
  }
16
+ const INVALID_INBOUND_TARGET_ERROR = "inbound target channel and target are required";
17
+ function invalidInboundTarget() {
18
+ return {
19
+ channel: "",
20
+ target: "",
21
+ valid: false,
22
+ error: INVALID_INBOUND_TARGET_ERROR,
23
+ };
24
+ }
25
+ function displayTargetId(target) {
26
+ return typeof target.id === "string" ? target.id.trim() || undefined : undefined;
27
+ }
28
+ function normalizeConfiguredTarget(target) {
29
+ if (!target || typeof target !== "object" || Array.isArray(target)) {
30
+ return invalidInboundTarget();
31
+ }
32
+ const configuredTarget = target;
33
+ const channel = typeof configuredTarget.channel === "string" ? configuredTarget.channel.trim() : "";
34
+ const destination = typeof configuredTarget.target === "string" ? configuredTarget.target.trim() : "";
35
+ const normalized = {
36
+ id: displayTargetId(configuredTarget),
37
+ channel,
38
+ target: destination,
39
+ valid: Boolean(channel && destination),
40
+ };
41
+ if (!normalized.valid) {
42
+ normalized.error = INVALID_INBOUND_TARGET_ERROR;
43
+ }
44
+ return normalized;
45
+ }
46
+ function effectiveInboundTargets(config) {
47
+ if (Array.isArray(config.inboundTargets)) {
48
+ const seen = new Set();
49
+ const targets = [];
50
+ for (const target of config.inboundTargets) {
51
+ const normalized = normalizeConfiguredTarget(target);
52
+ const key = `${normalized.channel}\0${normalized.target}`;
53
+ if (normalized.valid && seen.has(key)) {
54
+ continue;
55
+ }
56
+ if (normalized.valid) {
57
+ seen.add(key);
58
+ }
59
+ targets.push(normalized);
60
+ }
61
+ return targets;
62
+ }
63
+ if (!config.inboundChannel || !config.inboundTarget) {
64
+ return [];
65
+ }
66
+ return [
67
+ {
68
+ channel: config.inboundChannel,
69
+ target: config.inboundTarget,
70
+ valid: true,
71
+ },
72
+ ];
73
+ }
74
+ function firstAttemptedResult(results) {
75
+ return results.find((result) => result.ok) ?? results[0];
76
+ }
16
77
  export function createInstanceRouter(options) {
17
78
  const { mesh, store, delivery } = options;
18
79
  const config = options.config ?? {};
@@ -21,7 +82,10 @@ export function createInstanceRouter(options) {
21
82
  const announcedPeers = new Set();
22
83
  const pendingAcks = new Map();
23
84
  const deliveryCache = new Map();
85
+ const inboundTimeoutControllers = new Set();
86
+ const activeAckSends = new Set();
24
87
  const unsubs = [];
88
+ let stopped = false;
25
89
  function localInstanceId() {
26
90
  const identity = mesh.getInstanceIdentity();
27
91
  if (!identity) {
@@ -95,14 +159,191 @@ export function createInstanceRouter(options) {
95
159
  }
96
160
  }
97
161
  async function sendAck(peerId, ack) {
98
- await mesh.sendStructuredMessage(peerId, {
162
+ if (stopped) {
163
+ return;
164
+ }
165
+ const send = mesh.sendStructuredMessage(peerId, {
99
166
  id: crypto.randomUUID(),
100
167
  type: "delivery-ack",
101
168
  to: peerId,
102
169
  payload: JSON.stringify(ack),
103
170
  });
171
+ activeAckSends.add(send);
172
+ try {
173
+ await send;
174
+ }
175
+ finally {
176
+ activeAckSends.delete(send);
177
+ }
178
+ }
179
+ function deliveryCacheKey(payload, msg) {
180
+ return [
181
+ payload.fromInstanceId,
182
+ msg.from,
183
+ payload.toInstanceId,
184
+ payload.messageId,
185
+ ].join("\0");
186
+ }
187
+ function buildAckFromDeliveryState(messageId, state) {
188
+ const selected = firstAttemptedResult(state.results);
189
+ return {
190
+ ackFor: messageId,
191
+ ok: state.results.some((result) => result.ok),
192
+ inboundChannel: selected?.channel,
193
+ inboundTarget: selected?.target,
194
+ deliveredAt: Date.now(),
195
+ error: state.results.every((result) => !result.ok)
196
+ ? state.results.map((result) => result.error).filter(Boolean).join("; ") ||
197
+ "inbound delivery failed"
198
+ : undefined,
199
+ results: state.results,
200
+ };
201
+ }
202
+ async function deliverAndBuildAck(payload, msg, state) {
203
+ const targets = effectiveInboundTargets(config);
204
+ if (targets.length === 0) {
205
+ return {
206
+ ackFor: payload.messageId,
207
+ ok: false,
208
+ deliveredAt: Date.now(),
209
+ error: "inbound delivery is not configured",
210
+ results: [],
211
+ };
212
+ }
213
+ const metadata = payload.metadata;
214
+ for (const target of targets) {
215
+ if (state.timedOut) {
216
+ break;
217
+ }
218
+ if (!target.valid) {
219
+ state.results.push({
220
+ id: target.id,
221
+ channel: target.channel,
222
+ target: target.target,
223
+ ok: false,
224
+ error: target.error ?? "inbound target channel and target are required",
225
+ });
226
+ continue;
227
+ }
228
+ state.currentTarget = target;
229
+ try {
230
+ const result = await delivery.deliver({
231
+ channel: target.channel,
232
+ target: target.target,
233
+ text: payload.text,
234
+ metadata: {
235
+ fromInstanceId: payload.fromInstanceId,
236
+ fromPeerId: msg.from,
237
+ p2pMessageId: payload.messageId,
238
+ allowAgentAutoReply: metadata?.allowAgentAutoReply === true,
239
+ replyToInstanceId: payload.fromInstanceId,
240
+ replyTool: "p2p_send_instance_message",
241
+ },
242
+ });
243
+ if (state.timedOut) {
244
+ break;
245
+ }
246
+ state.results.push({
247
+ id: target.id,
248
+ channel: result.channel,
249
+ target: result.target,
250
+ ok: result.ok,
251
+ error: result.error,
252
+ });
253
+ }
254
+ catch (error) {
255
+ if (state.timedOut) {
256
+ break;
257
+ }
258
+ state.results.push({
259
+ id: target.id,
260
+ channel: target.channel,
261
+ target: target.target,
262
+ ok: false,
263
+ error: summarizeError(error),
264
+ });
265
+ }
266
+ finally {
267
+ if (state.currentTarget === target) {
268
+ state.currentTarget = undefined;
269
+ }
270
+ }
271
+ }
272
+ return buildAckFromDeliveryState(payload.messageId, state);
273
+ }
274
+ function buildInboundTimeoutAck(messageId, state) {
275
+ const error = `inbound delivery timeout after ${ackTimeoutMs}ms`;
276
+ const results = state.results.slice();
277
+ const currentTarget = state.currentTarget;
278
+ if (currentTarget) {
279
+ results.push({
280
+ id: currentTarget.id,
281
+ channel: currentTarget.channel,
282
+ target: currentTarget.target,
283
+ ok: false,
284
+ error: currentTarget.valid
285
+ ? error
286
+ : currentTarget.error ?? "inbound target channel and target are required",
287
+ });
288
+ }
289
+ const selected = firstAttemptedResult(results);
290
+ return {
291
+ ackFor: messageId,
292
+ ok: results.some((result) => result.ok),
293
+ inboundChannel: selected?.channel,
294
+ inboundTarget: selected?.target,
295
+ deliveredAt: Date.now(),
296
+ error,
297
+ results,
298
+ };
299
+ }
300
+ function withInboundDeliveryTimeout(promise, messageId, state) {
301
+ return new Promise((resolve) => {
302
+ let settled = false;
303
+ const settle = (ack) => {
304
+ if (settled)
305
+ return;
306
+ settled = true;
307
+ clearTimeout(timer);
308
+ inboundTimeoutControllers.delete(controller);
309
+ resolve(ack);
310
+ };
311
+ const timer = setTimeout(() => {
312
+ state.timedOut = true;
313
+ settle(buildInboundTimeoutAck(messageId, state));
314
+ }, ackTimeoutMs);
315
+ const controller = {
316
+ stop() {
317
+ state.timedOut = true;
318
+ settle({
319
+ ackFor: messageId,
320
+ ok: false,
321
+ deliveredAt: Date.now(),
322
+ error: "instance router stopped",
323
+ results: state.results,
324
+ });
325
+ },
326
+ };
327
+ inboundTimeoutControllers.add(controller);
328
+ promise
329
+ .then((ack) => {
330
+ settle(ack);
331
+ })
332
+ .catch((error) => {
333
+ settle({
334
+ ackFor: messageId,
335
+ ok: false,
336
+ deliveredAt: Date.now(),
337
+ error: summarizeError(error),
338
+ results: [],
339
+ });
340
+ });
341
+ });
104
342
  }
105
343
  async function handleUserMessage(msg) {
344
+ if (stopped) {
345
+ return;
346
+ }
106
347
  const payload = parsePayload(msg);
107
348
  if (!payload ||
108
349
  !isNonEmptyString(payload.messageId) ||
@@ -117,6 +358,9 @@ export function createInstanceRouter(options) {
117
358
  return;
118
359
  }
119
360
  const senderRoute = await store.resolve(payload.fromInstanceId);
361
+ if (stopped) {
362
+ return;
363
+ }
120
364
  if (!senderRoute || senderRoute.peerId !== msg.from) {
121
365
  logger?.warn?.(`[libp2p-mesh] Ignoring user-message from ${msg.from}; instance ${payload.fromInstanceId} is not routed to that peer`);
122
366
  return;
@@ -126,56 +370,78 @@ export function createInstanceRouter(options) {
126
370
  logger?.warn?.(`[libp2p-mesh] Ignoring user-message for ${payload.toInstanceId}; local instance is ${localId}`);
127
371
  return;
128
372
  }
129
- const cached = deliveryCache.get(payload.messageId);
373
+ const cacheKey = deliveryCacheKey(payload, msg);
374
+ const cached = deliveryCache.get(cacheKey);
130
375
  if (cached) {
131
- await sendAck(cached.peerId, cached.payload);
376
+ const ack = cached.payload ?? (await cached.promise);
377
+ if (!ack) {
378
+ throw new Error(`delivery cache entry for ${payload.messageId} has no ACK payload`);
379
+ }
380
+ if (stopped) {
381
+ return;
382
+ }
383
+ await sendAck(msg.from, ack);
132
384
  return;
133
385
  }
134
- let ack;
135
- if (!config.inboundChannel || !config.inboundTarget) {
136
- ack = {
386
+ if (pendingDeliveryCount() >= MAX_DELIVERY_CACHE_ENTRIES) {
387
+ const ack = {
137
388
  ackFor: payload.messageId,
138
389
  ok: false,
139
- inboundChannel: config.inboundChannel,
140
- inboundTarget: config.inboundTarget,
141
390
  deliveredAt: Date.now(),
142
- error: "inbound delivery is not configured",
391
+ error: `too many pending inbound deliveries (${MAX_DELIVERY_CACHE_ENTRIES})`,
392
+ results: [],
143
393
  };
394
+ deliveryCache.set(cacheKey, { payload: ack });
395
+ trimDeliveryCache(cacheKey);
396
+ if (stopped) {
397
+ return;
398
+ }
399
+ await sendAck(msg.from, ack);
400
+ return;
144
401
  }
145
- else {
146
- const metadata = payload.metadata;
147
- const result = await delivery.deliver({
148
- channel: config.inboundChannel,
149
- target: config.inboundTarget,
150
- text: payload.text,
151
- metadata: {
152
- fromInstanceId: payload.fromInstanceId,
153
- fromPeerId: msg.from,
154
- p2pMessageId: payload.messageId,
155
- allowAgentAutoReply: metadata?.allowAgentAutoReply === true,
156
- replyToInstanceId: payload.fromInstanceId,
157
- replyTool: "p2p_send_instance_message",
158
- },
159
- });
160
- ack = {
161
- ackFor: payload.messageId,
162
- ok: result.ok,
163
- inboundChannel: result.channel,
164
- inboundTarget: result.target,
165
- deliveredAt: Date.now(),
166
- error: result.error,
167
- };
402
+ const deliveryState = {
403
+ timedOut: false,
404
+ results: [],
405
+ };
406
+ const deliveryPromise = Promise.resolve()
407
+ .then(() => deliverAndBuildAck(payload, msg, deliveryState))
408
+ .finally(() => {
409
+ const entry = deliveryCache.get(cacheKey);
410
+ if (entry) {
411
+ entry.inFlight = false;
412
+ }
413
+ });
414
+ const ackPromise = withInboundDeliveryTimeout(deliveryPromise, payload.messageId, deliveryState);
415
+ const cacheEntry = { inFlight: true, promise: ackPromise };
416
+ deliveryCache.set(cacheKey, cacheEntry);
417
+ trimDeliveryCache();
418
+ const ack = await ackPromise;
419
+ if (stopped) {
420
+ return;
168
421
  }
169
- deliveryCache.set(payload.messageId, { peerId: msg.from, payload: ack });
422
+ cacheEntry.payload = ack;
170
423
  trimDeliveryCache();
424
+ if (stopped) {
425
+ return;
426
+ }
171
427
  await sendAck(msg.from, ack);
172
428
  }
173
- function trimDeliveryCache() {
429
+ function pendingDeliveryCount() {
430
+ let count = 0;
431
+ for (const entry of deliveryCache.values()) {
432
+ if (entry.inFlight)
433
+ count += 1;
434
+ }
435
+ return count;
436
+ }
437
+ function trimDeliveryCache(protectedKey) {
174
438
  while (deliveryCache.size > MAX_DELIVERY_CACHE_ENTRIES) {
175
- const oldestKey = deliveryCache.keys().next().value;
176
- if (!oldestKey)
439
+ const settledKeys = Array.from(deliveryCache)
440
+ .filter(([key, entry]) => key !== protectedKey && entry.payload && !entry.inFlight)
441
+ .map(([key]) => key);
442
+ if (settledKeys.length === 0)
177
443
  return;
178
- deliveryCache.delete(oldestKey);
444
+ deliveryCache.delete(settledKeys[0]);
179
445
  }
180
446
  }
181
447
  function handleAck(msg) {
@@ -211,6 +477,7 @@ export function createInstanceRouter(options) {
211
477
  }
212
478
  }
213
479
  async function start() {
480
+ stopped = false;
214
481
  unsubs.push(mesh.onMessage((msg) => {
215
482
  handleMessage(msg).catch((error) => {
216
483
  logger?.error?.(`[libp2p-mesh] Instance router message error: ${summarizeError(error)}`);
@@ -224,9 +491,14 @@ export function createInstanceRouter(options) {
224
491
  await announceToConnectedPeers();
225
492
  }
226
493
  async function stop() {
494
+ stopped = true;
227
495
  for (const unsub of unsubs.splice(0)) {
228
496
  unsub();
229
497
  }
498
+ for (const controller of Array.from(inboundTimeoutControllers)) {
499
+ controller.stop();
500
+ }
501
+ inboundTimeoutControllers.clear();
230
502
  for (const [messageId, pending] of pendingAcks) {
231
503
  clearTimeout(pending.timer);
232
504
  pending.resolve({
@@ -238,6 +510,8 @@ export function createInstanceRouter(options) {
238
510
  }
239
511
  pendingAcks.clear();
240
512
  deliveryCache.clear();
513
+ await Promise.allSettled([...activeAckSends]);
514
+ activeAckSends.clear();
241
515
  }
242
516
  async function listInstances() {
243
517
  return store.list();
@@ -311,6 +585,8 @@ export function createInstanceRouter(options) {
311
585
  toPeerId: route.peerId,
312
586
  ackMessageId: ack.ackFor,
313
587
  inboundChannel: ack.inboundChannel,
588
+ inboundTarget: ack.inboundTarget,
589
+ deliveryResults: ack.results,
314
590
  error: ack.error,
315
591
  };
316
592
  }
@@ -1,6 +1,6 @@
1
1
  import { createLibp2pMeshChannel } from "./channel.js";
2
2
  import { handleP2PInbound } from "./inbound.js";
3
- import { createOpenClawCliInboundDelivery } from "./inbound-delivery.js";
3
+ import { createOpenClawRuntimeInboundDelivery } from "./inbound-delivery.js";
4
4
  import { createInstancePeerStore } from "./instance-peer-store.js";
5
5
  import { createInstanceRouter } from "./instance-router.js";
6
6
  import { createMeshNetwork } from "./mesh.js";
@@ -14,7 +14,11 @@ export function registerLibp2pMesh(api) {
14
14
  logger: api.logger,
15
15
  });
16
16
  const store = createInstancePeerStore({ logger: api.logger });
17
- const delivery = createOpenClawCliInboundDelivery({ logger: api.logger });
17
+ const delivery = createOpenClawRuntimeInboundDelivery({
18
+ config: api.config,
19
+ loadAdapter: api.runtime.channel.outbound.loadAdapter,
20
+ logger: api.logger,
21
+ });
18
22
  const router = createInstanceRouter({
19
23
  mesh,
20
24
  store,
@@ -22,6 +26,7 @@ export function registerLibp2pMesh(api) {
22
26
  config,
23
27
  logger: api.logger,
24
28
  });
29
+ const channel = createLibp2pMeshChannel(mesh);
25
30
  // 1. Register Service (manages libp2p node lifecycle)
26
31
  api.registerService({
27
32
  id: "libp2p-mesh",
@@ -33,7 +38,32 @@ export function registerLibp2pMesh(api) {
33
38
  await mesh.start();
34
39
  await router.start();
35
40
  unsubscribeInbound = mesh.onMessage((msg) => {
36
- if (msg.type === "direct" || msg.type === "broadcast" || msg.type === "agent-sync") {
41
+ if (msg.type === "direct" || msg.type === "broadcast") {
42
+ const sendToChannel = async (_channelId, _target, text) => {
43
+ if (!config?.inboundChannel || !config?.inboundTarget) {
44
+ api.logger.warn?.("[libp2p-mesh] inboundChannel/inboundTarget not configured; direct message logged only.");
45
+ return;
46
+ }
47
+ const result = await delivery.deliver({
48
+ channel: config.inboundChannel,
49
+ target: config.inboundTarget,
50
+ text,
51
+ metadata: {
52
+ fromInstanceId: msg.instanceId ?? msg.from,
53
+ fromPeerId: msg.from,
54
+ p2pMessageId: msg.id,
55
+ allowAgentAutoReply: false,
56
+ replyToInstanceId: msg.instanceId ?? msg.from,
57
+ replyTool: "p2p_send_instance_message",
58
+ },
59
+ });
60
+ if (!result.ok) {
61
+ api.logger.error?.(`[libp2p-mesh] Failed to forward direct message from ${msg.from}: ${result.error}`);
62
+ }
63
+ };
64
+ handleP2PInbound(msg, { logger: api.logger, sendToChannel });
65
+ }
66
+ else if (msg.type === "agent-sync") {
37
67
  handleP2PInbound(msg, { logger: api.logger });
38
68
  }
39
69
  });
@@ -65,7 +95,7 @@ export function registerLibp2pMesh(api) {
65
95
  });
66
96
  // 2. Register Channel (lightweight debugging surface)
67
97
  api.registerChannel({
68
- plugin: createLibp2pMeshChannel(mesh),
98
+ plugin: channel,
69
99
  });
70
100
  // 3. Register Agent Tools
71
101
  const tools = buildP2PTools(mesh, router);