libp2p-mesh 2026.6.12 → 2026.6.14

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.
Files changed (43) hide show
  1. package/README.md +161 -0
  2. package/dist/src/agent-tools.d.ts +120 -1
  3. package/dist/src/agent-tools.js +153 -0
  4. package/dist/src/debug-cli.d.ts +21 -0
  5. package/dist/src/debug-cli.js +54 -0
  6. package/dist/src/debug-wizard.d.ts +19 -0
  7. package/dist/src/debug-wizard.js +41 -0
  8. package/dist/src/instance-peer-store.js +35 -1
  9. package/dist/src/instance-router.d.ts +2 -8
  10. package/dist/src/instance-router.js +139 -7
  11. package/dist/src/plugin.d.ts +7 -0
  12. package/dist/src/plugin.js +105 -50
  13. package/dist/src/profile-cli.d.ts +29 -0
  14. package/dist/src/profile-cli.js +49 -0
  15. package/dist/src/profile-wizard.d.ts +20 -0
  16. package/dist/src/profile-wizard.js +141 -0
  17. package/dist/src/setup-cli.d.ts +19 -0
  18. package/dist/src/setup-cli.js +32 -28
  19. package/dist/src/setup-config.d.ts +4 -1
  20. package/dist/src/setup-config.js +27 -0
  21. package/dist/src/types.d.ts +67 -0
  22. package/dist/src/user-attributes.d.ts +6 -0
  23. package/dist/src/user-attributes.js +92 -0
  24. package/dist/src/user-md-attributes.d.ts +12 -0
  25. package/dist/src/user-md-attributes.js +202 -0
  26. package/dist/src/user-profile-store.d.ts +25 -0
  27. package/dist/src/user-profile-store.js +187 -0
  28. package/openclaw.plugin.json +14 -1
  29. package/package.json +1 -1
  30. package/src/agent-tools.ts +187 -1
  31. package/src/debug-cli.ts +93 -0
  32. package/src/debug-wizard.ts +64 -0
  33. package/src/instance-peer-store.ts +41 -1
  34. package/src/instance-router.ts +188 -18
  35. package/src/plugin.ts +133 -59
  36. package/src/profile-cli.ts +88 -0
  37. package/src/profile-wizard.ts +204 -0
  38. package/src/setup-cli.ts +40 -29
  39. package/src/setup-config.ts +35 -1
  40. package/src/types.ts +73 -0
  41. package/src/user-attributes.ts +122 -0
  42. package/src/user-md-attributes.ts +256 -0
  43. package/src/user-profile-store.ts +259 -0
package/README.md CHANGED
@@ -10,6 +10,7 @@ P2P mesh network plugin for OpenClaw. Enables direct peer-to-peer communication
10
10
  - **Bootstrap Mode** — Optional static bootstrap peer list for non-LAN scenarios
11
11
  - **WebSocket Transport** — Optional WebSocket support for NAT/firewall-friendly connections
12
12
  - **NAT Traversal** — Built-in AutoNAT + UPnP + Circuit Relay v2 + DCUtR for peers behind home routers / firewalls
13
+ - **User Public Attributes** — Announce public tags and structured profile attributes so agents can dry-run and send to locally discovered instances by attribute
13
14
 
14
15
  ## Requirements
15
16
 
@@ -224,11 +225,34 @@ If `inboundTargets` is an empty array, inbound delivery is disabled. If `inbound
224
225
  | `relayList` | `string[]` | `[]` | Multiaddrs of relays to reserve a slot on |
225
226
  | `discoverRelays` | `number` | `0` | Auto-discover this many relays via content routing |
226
227
  | `announceAddrs` | `string[]` | `[]` | Extra multiaddrs to announce on top of auto-detected ones |
228
+ | `announceLogDetail` | `"off" \| "summary" \| "payload"` | `"summary"` | Controls instance announce logging. `summary` logs peer, instance, address count, and attribute count; `payload` also logs the full announce JSON; `off` disables only the new announce summary/payload logs and keeps legacy/basic info logs. |
227
229
  | `inboundChannel` | `string` | `undefined` | OpenClaw channel used to display inbound P2P user messages, for example `"feishu"` |
228
230
  | `inboundTarget` | `string` | `undefined` | OpenClaw channel target for inbound P2P messages, for example `user:ou_xxx` or `chat:oc_xxx` |
229
231
  | `inboundTargets` | `array` | `undefined` | Optional list of receiver-owned channel targets for inbound P2P user messages. When present, it overrides `inboundChannel`/`inboundTarget`; an empty array disables inbound delivery. |
230
232
  | `deliveryAckTimeoutMs` | `number` | `15000` | Timeout for waiting on remote channel delivery ACKs |
231
233
 
234
+ ### Announce Startup and Logging
235
+
236
+ During gateway startup, `libp2p-mesh` registers the instance router handlers and direct/broadcast inbound handlers before starting the mesh node. This makes early `instance-announce` messages observable as soon as peers connect, instead of waiting until after mesh startup has already completed.
237
+
238
+ Instance announce logs are controlled by `plugins.entries["libp2p-mesh"].config.announceLogDetail`:
239
+
240
+ - `summary` is the default. It logs send/receive direction, peer ID, instance ID, multiaddr count, and public attribute count. It does not print the full announce JSON.
241
+ - `off` disables the new announce summary and payload logs. It still keeps legacy/basic info logs such as sent announce lines and instance mapping updates, along with warnings and errors.
242
+ - `payload` logs the same summary plus the full announce JSON at debug level.
243
+
244
+ Use the debug command to inspect or change this value:
245
+
246
+ ```bash
247
+ openclaw libp2p-mesh debug
248
+ ```
249
+
250
+ Full payload logging is intended for short-lived troubleshooting only. Announce payloads can include `userPublicAttributes`, peer multiaddrs, the instance pubkey, and instance identity fields. After changing the setting, restart the gateway for the new logging level to take effect:
251
+
252
+ ```bash
253
+ openclaw gateway restart
254
+ ```
255
+
232
256
  ## NAT Traversal
233
257
 
234
258
  When both peers have a routable address (same LAN, public IPs, or working port-forwarding) no extra setup is needed. The defaults above kick in automatically:
@@ -443,6 +467,129 @@ The sender reports success only after the remote OpenClaw instance forwards the
443
467
 
444
468
  Tools are not configured in `openclaw.json`; they are registered automatically by the plugin through `api.registerTool()`.
445
469
 
470
+ ### User public attributes
471
+
472
+ `libp2p-mesh` can announce user public attributes with instance route announcements. These attributes help agents find matching OpenClaw instances after those instances have already been discovered through the mesh.
473
+
474
+ There are two sources:
475
+
476
+ - `USER.md` tags are extracted read-only at gateway startup. The plugin never edits `USER.md`.
477
+ - `user-profile.json` stores manually managed structured attributes such as group, project, role, skill, or a custom key.
478
+
479
+ Run the profile wizard to manage structured attributes:
480
+
481
+ ```bash
482
+ openclaw libp2p-mesh profile
483
+ ```
484
+
485
+ The wizard previews read-only `USER.md` tags and lets you add, edit, or remove only structured profile attributes. Tags extracted from `USER.md` are not written to `user-profile.json`; they are merged in memory with profile attributes and broadcast only in instance announce messages.
486
+
487
+ The default profile path is:
488
+
489
+ ```text
490
+ ~/.openclaw/libp2p/user-profile.json
491
+ ```
492
+
493
+ When `OPENCLAW_STATE_DIR` is set:
494
+
495
+ ```text
496
+ $OPENCLAW_STATE_DIR/libp2p/user-profile.json
497
+ ```
498
+
499
+ Example `user-profile.json`:
500
+
501
+ ```json
502
+ {
503
+ "version": 1,
504
+ "updatedAt": 1782180000000,
505
+ "attributes": [
506
+ {
507
+ "kind": "structured",
508
+ "key": "project",
509
+ "value": "openclaw",
510
+ "label": "project: openclaw",
511
+ "source": "profile"
512
+ },
513
+ {
514
+ "kind": "structured",
515
+ "key": "role",
516
+ "value": "maintainer",
517
+ "label": "role: maintainer",
518
+ "source": "profile"
519
+ }
520
+ ]
521
+ }
522
+ ```
523
+
524
+ Remote attributes are cached in plugin-managed instance state under `instance-peer.json.userPublicAttributes`:
525
+
526
+ ```json
527
+ {
528
+ "version": 1,
529
+ "updatedAt": 1782180000000,
530
+ "instances": {
531
+ "alice-mac@AQIDBAUGBweI.7a3f9e2b": {
532
+ "instanceId": "alice-mac@AQIDBAUGBweI.7a3f9e2b",
533
+ "peerId": "12D3KooW...",
534
+ "instanceName": "alice-mac",
535
+ "multiaddrs": ["/ip4/192.168.1.23/tcp/4001"],
536
+ "userPublicAttributes": [
537
+ {
538
+ "kind": "tag",
539
+ "value": "libp2p",
540
+ "label": "libp2p",
541
+ "source": "USER.md"
542
+ },
543
+ {
544
+ "kind": "structured",
545
+ "key": "project",
546
+ "value": "openclaw",
547
+ "label": "project: openclaw",
548
+ "source": "profile"
549
+ }
550
+ ],
551
+ "lastSeenAt": 1782180000000,
552
+ "lastAnnouncedAt": 1782180000000,
553
+ "source": "announce"
554
+ }
555
+ }
556
+ }
557
+ ```
558
+
559
+ Use `p2p_send_user_attribute_message` for attribute-based group messages. Always dry-run first, review the matched instances with the user, then send only after confirmation:
560
+
561
+ ```text
562
+ p2p_send_user_attribute_message({
563
+ "match": { "kind": "structured", "key": "project", "value": "openclaw" },
564
+ "message": "今晚同步一下进展",
565
+ "dryRun": true
566
+ })
567
+ ```
568
+
569
+ After confirming the dry-run targets:
570
+
571
+ ```text
572
+ p2p_send_user_attribute_message({
573
+ "match": { "kind": "structured", "key": "project", "value": "openclaw" },
574
+ "message": "今晚同步一下进展",
575
+ "dryRun": false
576
+ })
577
+ ```
578
+
579
+ Tag matches use only the tag value:
580
+
581
+ ```text
582
+ p2p_send_user_attribute_message({
583
+ "match": { "kind": "tag", "value": "libp2p" },
584
+ "message": "libp2p 方向有个问题想确认",
585
+ "dryRun": true
586
+ })
587
+ ```
588
+
589
+ The first version matches only instances already present in the local `instance-peer.json` discovery cache. It does not search the whole network or ask disconnected peers for more users.
590
+
591
+ Privacy boundary: public attributes are broadcast with instance announce messages to peers your gateway connects to. Do not put private, sensitive, or access-controlled information in `USER.md` tags or `user-profile.json` structured attributes.
592
+
446
593
  ## Troubleshooting
447
594
 
448
595
  ### Peers do not discover each other
@@ -492,6 +639,20 @@ Peer connection and disconnection are logged at `info` level:
492
639
 
493
640
  If these lines are missing, confirm the gateway is running with normal info logs enabled and that both instances are on the same mDNS, bootstrap, or relay network.
494
641
 
642
+ ### Instance announce routes are missing between two machines
643
+
644
+ If peers connect but sending by OpenClaw instance ID fails or `instance-peer.json` is not updated, first confirm both gateways were restarted after the latest config change. On startup, the gateway now attaches the instance router plus inbound message handlers before starting the mesh, so early announces should be handled once the peer connection appears.
645
+
646
+ For a short debug session on both computers:
647
+
648
+ 1. Run `openclaw libp2p-mesh debug`.
649
+ 2. Set `announceLogDetail` to `payload` and confirm the privacy warning.
650
+ 3. Restart both gateways with `openclaw gateway restart`.
651
+ 4. Watch for summary lines and debug lines containing full announce payload JSON.
652
+ 5. Return to `summary` or `off` with `openclaw libp2p-mesh debug`, then restart again.
653
+
654
+ Full payload logs may expose `userPublicAttributes`, multiaddrs, pubkey, and instance identity, so avoid sharing these logs outside the debugging context.
655
+
495
656
  ## Architecture
496
657
 
497
658
  ```
@@ -1,4 +1,13 @@
1
- import type { DeliveryTargetResult, InstanceRouter, MeshNetwork } from "./types.js";
1
+ import type { DeliveryTargetResult, InstanceRouter, MeshNetwork, UserPublicAttribute } from "./types.js";
2
+ type SendUserAttributeToolParams = {
3
+ match?: {
4
+ kind?: unknown;
5
+ key?: unknown;
6
+ value?: unknown;
7
+ };
8
+ message?: unknown;
9
+ dryRun?: unknown;
10
+ };
2
11
  export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter): ({
3
12
  name: string;
4
13
  label: string;
@@ -16,6 +25,8 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
16
25
  };
17
26
  topic?: undefined;
18
27
  instanceId?: undefined;
28
+ match?: undefined;
29
+ dryRun?: undefined;
19
30
  };
20
31
  required: string[];
21
32
  };
@@ -62,6 +73,8 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
62
73
  };
63
74
  peerId?: undefined;
64
75
  instanceId?: undefined;
76
+ match?: undefined;
77
+ dryRun?: undefined;
65
78
  };
66
79
  required: string[];
67
80
  };
@@ -102,6 +115,8 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
102
115
  message?: undefined;
103
116
  topic?: undefined;
104
117
  instanceId?: undefined;
118
+ match?: undefined;
119
+ dryRun?: undefined;
105
120
  };
106
121
  required?: undefined;
107
122
  };
@@ -141,6 +156,8 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
141
156
  message?: undefined;
142
157
  topic?: undefined;
143
158
  instanceId?: undefined;
159
+ match?: undefined;
160
+ dryRun?: undefined;
144
161
  };
145
162
  required?: undefined;
146
163
  };
@@ -189,6 +206,8 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
189
206
  message?: undefined;
190
207
  topic?: undefined;
191
208
  instanceId?: undefined;
209
+ match?: undefined;
210
+ dryRun?: undefined;
192
211
  };
193
212
  required?: undefined;
194
213
  };
@@ -230,6 +249,8 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
230
249
  message?: undefined;
231
250
  topic?: undefined;
232
251
  instanceId?: undefined;
252
+ match?: undefined;
253
+ dryRun?: undefined;
233
254
  };
234
255
  required?: undefined;
235
256
  };
@@ -258,6 +279,7 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
258
279
  instanceName?: string;
259
280
  multiaddrs: string[];
260
281
  pubkey?: string;
282
+ userPublicAttributes?: UserPublicAttribute[];
261
283
  lastSeenAt: number;
262
284
  lastAnnouncedAt: number;
263
285
  source: "announce";
@@ -294,6 +316,8 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
294
316
  peerId?: undefined;
295
317
  message?: undefined;
296
318
  topic?: undefined;
319
+ match?: undefined;
320
+ dryRun?: undefined;
297
321
  };
298
322
  required: string[];
299
323
  };
@@ -369,6 +393,8 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
369
393
  };
370
394
  peerId?: undefined;
371
395
  topic?: undefined;
396
+ match?: undefined;
397
+ dryRun?: undefined;
372
398
  };
373
399
  required: string[];
374
400
  };
@@ -430,4 +456,97 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
430
456
  };
431
457
  isError?: undefined;
432
458
  }>;
459
+ } | {
460
+ name: string;
461
+ label: string;
462
+ description: string;
463
+ parameters: {
464
+ type: "object";
465
+ properties: {
466
+ match: {
467
+ type: "object";
468
+ description: string;
469
+ oneOf: ({
470
+ type: "object";
471
+ additionalProperties: boolean;
472
+ properties: {
473
+ kind: {
474
+ const: "tag";
475
+ };
476
+ value: {
477
+ type: "string";
478
+ description: string;
479
+ };
480
+ key?: undefined;
481
+ };
482
+ required: string[];
483
+ } | {
484
+ type: "object";
485
+ additionalProperties: boolean;
486
+ properties: {
487
+ kind: {
488
+ const: "structured";
489
+ };
490
+ key: {
491
+ type: "string";
492
+ description: string;
493
+ };
494
+ value: {
495
+ type: "string";
496
+ description: string;
497
+ };
498
+ };
499
+ required: string[];
500
+ })[];
501
+ };
502
+ message: {
503
+ type: "string";
504
+ description: string;
505
+ };
506
+ dryRun: {
507
+ type: "boolean";
508
+ description: string;
509
+ };
510
+ peerId?: undefined;
511
+ topic?: undefined;
512
+ instanceId?: undefined;
513
+ };
514
+ required: string[];
515
+ };
516
+ execute(_toolCallId: string, params: SendUserAttributeToolParams): Promise<{
517
+ content: {
518
+ type: "text";
519
+ text: string;
520
+ }[];
521
+ details: {
522
+ initialized: boolean;
523
+ error?: undefined;
524
+ };
525
+ isError: boolean;
526
+ } | {
527
+ content: {
528
+ type: "text";
529
+ text: string;
530
+ }[];
531
+ details: {
532
+ error: string;
533
+ initialized?: undefined;
534
+ };
535
+ isError: boolean;
536
+ } | {
537
+ content: {
538
+ type: "text";
539
+ text: string;
540
+ }[];
541
+ details: import("./types.js").UserAttributeMessageResult;
542
+ isError?: undefined;
543
+ } | {
544
+ content: {
545
+ type: "text";
546
+ text: string;
547
+ }[];
548
+ details: import("./types.js").UserAttributeMessageResult;
549
+ isError: boolean | undefined;
550
+ }>;
433
551
  })[];
552
+ export {};
@@ -12,6 +12,49 @@ function formatDeliveryResults(instanceId, delivered, results) {
12
12
  });
13
13
  return [heading, ...lines].join("\n");
14
14
  }
15
+ function attributeLabel(attribute) {
16
+ if (attribute.kind === "tag") {
17
+ return `tag:${attribute.value}`;
18
+ }
19
+ return `${attribute.key}:${attribute.value}`;
20
+ }
21
+ function instanceTargetLabel(target) {
22
+ const name = target.instanceName ? ` (${target.instanceName})` : "";
23
+ return `${target.instanceId}${name} -> ${target.peerId}`;
24
+ }
25
+ function formatUserAttributeTargets(targets) {
26
+ return targets.map((target) => `${instanceTargetLabel(target)} [${attributeLabel(target.matchedAttribute)}]`);
27
+ }
28
+ function formatUserAttributeResults(results) {
29
+ return results.map((result) => {
30
+ let status = "已送达";
31
+ if (!result.sent) {
32
+ status = `发送失败:${result.error ?? "unknown error"}`;
33
+ }
34
+ else if (!result.delivered) {
35
+ status = `投递失败:${result.error ?? "unknown error"}`;
36
+ }
37
+ return `${instanceTargetLabel(result)}:${status}`;
38
+ });
39
+ }
40
+ function normalizeUserAttributeMatch(params) {
41
+ const match = params.match;
42
+ if (!match || typeof match !== "object") {
43
+ return "match is required.";
44
+ }
45
+ if (match.kind === "tag") {
46
+ const value = typeof match.value === "string" ? match.value.trim() : "";
47
+ return value ? { kind: "tag", value } : "match.value is required for tag matches.";
48
+ }
49
+ if (match.kind === "structured") {
50
+ const key = typeof match.key === "string" ? match.key.trim() : "";
51
+ const value = typeof match.value === "string" ? match.value.trim() : "";
52
+ return key && value
53
+ ? { kind: "structured", key, value }
54
+ : "match.key and match.value are required for structured matches.";
55
+ }
56
+ return 'match.kind must be "tag" or "structured".';
57
+ }
15
58
  export function buildP2PTools(mesh, router) {
16
59
  return [
17
60
  {
@@ -369,5 +412,115 @@ export function buildP2PTools(mesh, router) {
369
412
  };
370
413
  },
371
414
  },
415
+ {
416
+ name: "p2p_send_user_attribute_message",
417
+ label: "P2P Send User Attribute Message",
418
+ description: "Send a user message to discovered OpenClaw instances matching a public user attribute. Always run a dry run with dryRun=true first and ask the user to confirm targets before group sending.",
419
+ parameters: {
420
+ type: "object",
421
+ properties: {
422
+ match: {
423
+ type: "object",
424
+ description: 'User public attribute match. Use { "kind": "tag", "value": "..." } for USER.md tags or { "kind": "structured", "key": "...", "value": "..." } for profile attributes.',
425
+ oneOf: [
426
+ {
427
+ type: "object",
428
+ additionalProperties: false,
429
+ properties: {
430
+ kind: { const: "tag" },
431
+ value: {
432
+ type: "string",
433
+ description: "Tag value to match.",
434
+ },
435
+ },
436
+ required: ["kind", "value"],
437
+ },
438
+ {
439
+ type: "object",
440
+ additionalProperties: false,
441
+ properties: {
442
+ kind: { const: "structured" },
443
+ key: {
444
+ type: "string",
445
+ description: "Structured attribute key to match.",
446
+ },
447
+ value: {
448
+ type: "string",
449
+ description: "Structured attribute value to match.",
450
+ },
451
+ },
452
+ required: ["kind", "key", "value"],
453
+ },
454
+ ],
455
+ },
456
+ message: {
457
+ type: "string",
458
+ description: "Message content to send after dry-run target confirmation.",
459
+ },
460
+ dryRun: {
461
+ type: "boolean",
462
+ description: "Preview matching instances without sending. Run this before group sending.",
463
+ },
464
+ },
465
+ required: ["match", "message"],
466
+ },
467
+ async execute(_toolCallId, params) {
468
+ if (!router) {
469
+ return {
470
+ content: [{ type: "text", text: "Instance router is not initialized." }],
471
+ details: { initialized: false },
472
+ isError: true,
473
+ };
474
+ }
475
+ const match = normalizeUserAttributeMatch(params);
476
+ const message = typeof params.message === "string" ? params.message.trim() : "";
477
+ if (typeof match === "string" || !message) {
478
+ const error = typeof match === "string" ? match : "message is required.";
479
+ return {
480
+ content: [{ type: "text", text: error }],
481
+ details: { error },
482
+ isError: true,
483
+ };
484
+ }
485
+ const dryRun = params.dryRun === true;
486
+ const result = await router.sendUserAttributeMessage(match, message, { dryRun });
487
+ if (result.error) {
488
+ return {
489
+ content: [{ type: "text", text: result.error }],
490
+ details: result,
491
+ isError: true,
492
+ };
493
+ }
494
+ if (dryRun) {
495
+ const targetLines = formatUserAttributeTargets(result.targets ?? []);
496
+ return {
497
+ content: [
498
+ {
499
+ type: "text",
500
+ text: [
501
+ `Dry run matched ${result.matched} instance(s). No message was sent.`,
502
+ ...targetLines,
503
+ ].join("\n"),
504
+ },
505
+ ],
506
+ details: result,
507
+ };
508
+ }
509
+ const resultLines = formatUserAttributeResults(result.results ?? []);
510
+ return {
511
+ content: [
512
+ {
513
+ type: "text",
514
+ text: [
515
+ `Matched ${result.matched} instance(s); sent ${result.sent}; delivered ${result.delivered}; failed ${result.failed}.`,
516
+ ...resultLines,
517
+ ].join("\n"),
518
+ },
519
+ ],
520
+ details: result,
521
+ isError: result.failed > 0 ? true : undefined,
522
+ };
523
+ },
524
+ },
372
525
  ];
373
526
  }
@@ -0,0 +1,21 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
2
+ import type { OpenClawPluginCliContext } from "openclaw/plugin-sdk/plugin-runtime";
3
+ import type { SetupPrompter } from "./setup-wizard.js";
4
+ import type { AnnounceLogDetail } from "./types.js";
5
+ type CliRootCommand = {
6
+ command(name: string): {
7
+ description(text: string): {
8
+ action(handler: () => Promise<void>): void;
9
+ };
10
+ };
11
+ };
12
+ export type DebugConfigWriter = {
13
+ saveAnnounceLogDetail(detail: AnnounceLogDetail): Promise<void>;
14
+ };
15
+ export type DebugCliDeps = {
16
+ createPrompter?: (ctx: OpenClawPluginCliContext) => SetupPrompter;
17
+ createWriter?: (api: OpenClawPluginApi) => DebugConfigWriter;
18
+ };
19
+ export declare function registerLibp2pMeshDebugCli(api: OpenClawPluginApi, deps?: DebugCliDeps): void;
20
+ export declare function registerLibp2pMeshDebugCommand(root: CliRootCommand, api: OpenClawPluginApi, ctx: OpenClawPluginCliContext, deps?: DebugCliDeps): void;
21
+ export {};
@@ -0,0 +1,54 @@
1
+ import { createReadlinePrompter, LIBP2P_MESH_CLI_REGISTRATION, } from "./setup-cli.js";
2
+ import { applyAnnounceLogDetail, getAnnounceLogDetail, } from "./setup-config.js";
3
+ import { runDebugWizard } from "./debug-wizard.js";
4
+ const DEBUG_CLI_AFTER_WRITE = {
5
+ mode: "none",
6
+ reason: "libp2p-mesh debug config updated; restart manually to apply gateway changes.",
7
+ };
8
+ export function registerLibp2pMeshDebugCli(api, deps = {}) {
9
+ api.registerCli((ctx) => {
10
+ const root = ctx.program
11
+ .command("libp2p-mesh")
12
+ .description("Configure libp2p-mesh plugin.");
13
+ registerLibp2pMeshDebugCommand(root, api, ctx, deps);
14
+ }, LIBP2P_MESH_CLI_REGISTRATION);
15
+ }
16
+ export function registerLibp2pMeshDebugCommand(root, api, ctx, deps = {}) {
17
+ root
18
+ .command("debug")
19
+ .description("Manage libp2p-mesh debug logging config.")
20
+ .action(async () => {
21
+ const prompter = (deps.createPrompter?.(ctx) ?? createReadlinePrompter());
22
+ const writer = deps.createWriter?.(api) ?? createOpenClawDebugConfigWriter(api);
23
+ try {
24
+ const result = await runDebugWizard({
25
+ prompter,
26
+ current: getAnnounceLogDetail(ctx.config),
27
+ writer,
28
+ });
29
+ prompter.print(result.message);
30
+ }
31
+ finally {
32
+ prompter.close?.();
33
+ }
34
+ });
35
+ }
36
+ function createOpenClawDebugConfigWriter(api) {
37
+ return {
38
+ async saveAnnounceLogDetail(detail) {
39
+ await api.runtime.config.mutateConfigFile({
40
+ afterWrite: DEBUG_CLI_AFTER_WRITE,
41
+ mutate(draft) {
42
+ const nextConfig = applyAnnounceLogDetail(draft, detail);
43
+ replaceConfig(draft, nextConfig);
44
+ },
45
+ });
46
+ },
47
+ };
48
+ }
49
+ function replaceConfig(draft, nextConfig) {
50
+ for (const key of Object.keys(draft)) {
51
+ delete draft[key];
52
+ }
53
+ Object.assign(draft, structuredClone(nextConfig));
54
+ }
@@ -0,0 +1,19 @@
1
+ import { type SetupPrompter } from "./setup-wizard.js";
2
+ import type { AnnounceLogDetail } from "./types.js";
3
+ export type DebugPromptChoice = AnnounceLogDetail | "cancel";
4
+ export type RunDebugWizardOptions = {
5
+ prompter: SetupPrompter;
6
+ current: AnnounceLogDetail;
7
+ writer: {
8
+ saveAnnounceLogDetail(detail: AnnounceLogDetail): Promise<void>;
9
+ };
10
+ };
11
+ export type DebugWizardResult = {
12
+ status: "saved";
13
+ announceLogDetail: AnnounceLogDetail;
14
+ message: string;
15
+ } | {
16
+ status: "cancelled";
17
+ message: string;
18
+ };
19
+ export declare function runDebugWizard(options: RunDebugWizardOptions): Promise<DebugWizardResult>;
@@ -0,0 +1,41 @@
1
+ import { SetupCancelledError } from "./setup-wizard.js";
2
+ const CANCELLED_MESSAGE = "Debug configuration cancelled. No changes were written.";
3
+ const SAVED_MESSAGE = "Debug config updated.\n\nRestart the gateway to apply changes:\nopenclaw gateway restart";
4
+ export async function runDebugWizard(options) {
5
+ try {
6
+ options.prompter.print(`Current announceLogDetail: ${options.current}`);
7
+ const selected = await options.prompter.select("Set announceLogDetail:", [
8
+ { label: "summary: log peer, instance, address and attribute counts", value: "summary" },
9
+ { label: "off: disable announce summary/payload logs", value: "off" },
10
+ { label: "payload: log full announce JSON", value: "payload" },
11
+ { label: "Cancel", value: "cancel" },
12
+ ]);
13
+ if (selected === "cancel") {
14
+ return cancelledResult();
15
+ }
16
+ if (selected === "payload") {
17
+ const confirmed = await options.prompter.confirm("Full announce payload logs may include userPublicAttributes, multiaddrs, pubkey and instance identity. Enable payload logs?", false);
18
+ if (!confirmed) {
19
+ return cancelledResult();
20
+ }
21
+ }
22
+ await options.writer.saveAnnounceLogDetail(selected);
23
+ return {
24
+ status: "saved",
25
+ announceLogDetail: selected,
26
+ message: SAVED_MESSAGE,
27
+ };
28
+ }
29
+ catch (error) {
30
+ if (error instanceof SetupCancelledError) {
31
+ return cancelledResult();
32
+ }
33
+ throw error;
34
+ }
35
+ }
36
+ function cancelledResult() {
37
+ return {
38
+ status: "cancelled",
39
+ message: CANCELLED_MESSAGE,
40
+ };
41
+ }