libp2p-mesh 2026.6.16 → 2026.6.18

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/README.md CHANGED
@@ -572,7 +572,7 @@ Use `p2p_send_user_attribute_message` for attribute-based group messages. Always
572
572
 
573
573
  ```text
574
574
  p2p_send_user_attribute_message({
575
- "match": { "kind": "structured", "key": "project", "value": "openclaw" },
575
+ "selector": "project=openclaw",
576
576
  "message": "今晚同步一下进展",
577
577
  "dryRun": true
578
578
  })
@@ -582,17 +582,17 @@ After confirming the dry-run targets:
582
582
 
583
583
  ```text
584
584
  p2p_send_user_attribute_message({
585
- "match": { "kind": "structured", "key": "project", "value": "openclaw" },
585
+ "selector": "project=openclaw",
586
586
  "message": "今晚同步一下进展",
587
587
  "dryRun": false
588
588
  })
589
589
  ```
590
590
 
591
- Tag matches use only the tag value:
591
+ Selectors use `key=value` for structured profile attributes. Tag matches use `tag:value` or `#value`. Bare selectors such as `实验室` are rejected because they are ambiguous; use `group=实验室` for a structured group or `tag:实验室` for a USER.md tag.
592
592
 
593
593
  ```text
594
594
  p2p_send_user_attribute_message({
595
- "match": { "kind": "tag", "value": "libp2p" },
595
+ "selector": "#libp2p",
596
596
  "message": "libp2p 方向有个问题想确认",
597
597
  "dryRun": true
598
598
  })
@@ -1,5 +1,6 @@
1
1
  import type { DeliveryTargetResult, InstanceRouter, MeshNetwork, UserPublicAttribute } from "./types.js";
2
2
  type SendUserAttributeToolParams = {
3
+ selector?: unknown;
3
4
  match?: {
4
5
  kind?: unknown;
5
6
  key?: unknown;
@@ -25,6 +26,7 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
25
26
  };
26
27
  topic?: undefined;
27
28
  instanceId?: undefined;
29
+ selector?: undefined;
28
30
  match?: undefined;
29
31
  dryRun?: undefined;
30
32
  };
@@ -73,6 +75,7 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
73
75
  };
74
76
  peerId?: undefined;
75
77
  instanceId?: undefined;
78
+ selector?: undefined;
76
79
  match?: undefined;
77
80
  dryRun?: undefined;
78
81
  };
@@ -115,6 +118,7 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
115
118
  message?: undefined;
116
119
  topic?: undefined;
117
120
  instanceId?: undefined;
121
+ selector?: undefined;
118
122
  match?: undefined;
119
123
  dryRun?: undefined;
120
124
  };
@@ -156,6 +160,7 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
156
160
  message?: undefined;
157
161
  topic?: undefined;
158
162
  instanceId?: undefined;
163
+ selector?: undefined;
159
164
  match?: undefined;
160
165
  dryRun?: undefined;
161
166
  };
@@ -206,6 +211,7 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
206
211
  message?: undefined;
207
212
  topic?: undefined;
208
213
  instanceId?: undefined;
214
+ selector?: undefined;
209
215
  match?: undefined;
210
216
  dryRun?: undefined;
211
217
  };
@@ -249,6 +255,7 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
249
255
  message?: undefined;
250
256
  topic?: undefined;
251
257
  instanceId?: undefined;
258
+ selector?: undefined;
252
259
  match?: undefined;
253
260
  dryRun?: undefined;
254
261
  };
@@ -316,6 +323,7 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
316
323
  peerId?: undefined;
317
324
  message?: undefined;
318
325
  topic?: undefined;
326
+ selector?: undefined;
319
327
  match?: undefined;
320
328
  dryRun?: undefined;
321
329
  };
@@ -393,6 +401,7 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
393
401
  };
394
402
  peerId?: undefined;
395
403
  topic?: undefined;
404
+ selector?: undefined;
396
405
  match?: undefined;
397
406
  dryRun?: undefined;
398
407
  };
@@ -463,8 +472,13 @@ export declare function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter
463
472
  parameters: {
464
473
  type: "object";
465
474
  properties: {
475
+ selector: {
476
+ type: "string";
477
+ description: string;
478
+ };
466
479
  match: {
467
480
  type: "object";
481
+ deprecated: boolean;
468
482
  description: string;
469
483
  oneOf: ({
470
484
  type: "object";
@@ -37,10 +37,37 @@ function formatUserAttributeResults(results) {
37
37
  return `${instanceTargetLabel(result)}:${status}`;
38
38
  });
39
39
  }
40
+ function normalizeUserAttributeSelector(selector) {
41
+ const value = typeof selector === "string" ? selector.trim() : "";
42
+ if (!value) {
43
+ return "selector is required.";
44
+ }
45
+ if (value.startsWith("#")) {
46
+ const tagValue = value.slice(1).trim();
47
+ return tagValue ? { kind: "tag", value: tagValue } : "selector tag value is required.";
48
+ }
49
+ const tagMatch = /^tag\s*:\s*(.+)$/i.exec(value);
50
+ if (tagMatch) {
51
+ const tagValue = tagMatch[1].trim();
52
+ return tagValue ? { kind: "tag", value: tagValue } : "selector tag value is required.";
53
+ }
54
+ const structuredMatch = /^([A-Za-z][A-Za-z0-9_-]*)\s*=\s*(.+)$/.exec(value);
55
+ if (structuredMatch) {
56
+ const key = structuredMatch[1].trim();
57
+ const attributeValue = structuredMatch[2].trim();
58
+ return key && attributeValue
59
+ ? { kind: "structured", key, value: attributeValue }
60
+ : "selector key and value are required.";
61
+ }
62
+ return `selector "${value}" is ambiguous. Use "group=${value}" for structured group matching or "tag:${value}" for USER.md tags.`;
63
+ }
40
64
  function normalizeUserAttributeMatch(params) {
65
+ if (params.selector !== undefined) {
66
+ return normalizeUserAttributeSelector(params.selector);
67
+ }
41
68
  const match = params.match;
42
69
  if (!match || typeof match !== "object") {
43
- return "match is required.";
70
+ return "selector is required.";
44
71
  }
45
72
  if (match.kind === "tag") {
46
73
  const value = typeof match.value === "string" ? match.value.trim() : "";
@@ -415,13 +442,18 @@ export function buildP2PTools(mesh, router) {
415
442
  {
416
443
  name: "p2p_send_user_attribute_message",
417
444
  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.",
445
+ description: 'Send a user message to discovered OpenClaw instances matching a public user attribute selector. Use selectors like "group=实验室", "project=小龙虾", "tag:P2P", or "#P2P". Always run a dry run with dryRun=true first and ask the user to confirm targets before group sending.',
419
446
  parameters: {
420
447
  type: "object",
421
448
  properties: {
449
+ selector: {
450
+ type: "string",
451
+ description: 'Public attribute selector. Use "key=value" for structured profile attributes such as "group=实验室" or "project=小龙虾"; use "tag:value" or "#value" for USER.md tags. Bare values are rejected as ambiguous.',
452
+ },
422
453
  match: {
423
454
  type: "object",
424
- description: 'User public attribute match. Use { "kind": "tag", "value": "..." } for USER.md tags or { "kind": "structured", "key": "...", "value": "..." } for profile attributes.',
455
+ deprecated: true,
456
+ description: 'Deprecated compatibility field. Prefer selector. Use { "kind": "tag", "value": "..." } for USER.md tags or { "kind": "structured", "key": "...", "value": "..." } for profile attributes.',
425
457
  oneOf: [
426
458
  {
427
459
  type: "object",
@@ -462,7 +494,7 @@ export function buildP2PTools(mesh, router) {
462
494
  description: "Preview matching instances without sending. Run this before group sending.",
463
495
  },
464
496
  },
465
- required: ["match", "message"],
497
+ required: ["selector", "message"],
466
498
  },
467
499
  async execute(_toolCallId, params) {
468
500
  if (!router) {
@@ -80,11 +80,14 @@ function isTemplateText(value) {
80
80
  return trimmed.length === 0 || TEMPLATE_PATTERN.test(trimmed) || /^\[[^\]]+\]$/.test(trimmed);
81
81
  }
82
82
  function stripMarkdown(line) {
83
+ return stripMarkdownSyntax(line).replace(FIELD_PREFIX_PATTERN, "").trim();
84
+ }
85
+ function stripMarkdownSyntax(line) {
83
86
  return line
84
- .replace(FIELD_PREFIX_PATTERN, "")
85
87
  .replace(/`([^`]+)`/g, "$1")
86
88
  .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
87
89
  .replace(/[*_~#>]/g, " ")
90
+ .replace(/\s+/g, " ")
88
91
  .trim();
89
92
  }
90
93
  function trimCandidate(value) {
@@ -128,13 +131,14 @@ function looksLikeSentence(value) {
128
131
  return /[。.!?]/.test(value) || value.split(/\s+/).filter(Boolean).length > 4;
129
132
  }
130
133
  function fieldValue(line) {
131
- const match = line.match(FIELD_LINE_PATTERN);
134
+ const normalized = stripMarkdownSyntax(line);
135
+ const match = normalized.match(FIELD_LINE_PATTERN);
132
136
  if (!match) {
133
137
  return undefined;
134
138
  }
135
139
  return {
136
140
  field: (match[1] ?? "").toLowerCase(),
137
- value: stripMarkdown(match[2] ?? ""),
141
+ value: stripMarkdownSyntax(match[2] ?? ""),
138
142
  };
139
143
  }
140
144
  function isStableEnglishCandidate(value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libp2p-mesh",
3
- "version": "2026.6.16",
3
+ "version": "2026.6.18",
4
4
  "description": "OpenClaw libp2p P2P mesh network plugin for cross-instance agent communication",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -59,6 +59,7 @@ function formatUserAttributeResults(results: UserAttributeMessageDeliveryResult[
59
59
  }
60
60
 
61
61
  type SendUserAttributeToolParams = {
62
+ selector?: unknown;
62
63
  match?: {
63
64
  kind?: unknown;
64
65
  key?: unknown;
@@ -68,10 +69,43 @@ type SendUserAttributeToolParams = {
68
69
  dryRun?: unknown;
69
70
  };
70
71
 
72
+ function normalizeUserAttributeSelector(selector: unknown): UserAttributeMatch | string {
73
+ const value = typeof selector === "string" ? selector.trim() : "";
74
+ if (!value) {
75
+ return "selector is required.";
76
+ }
77
+
78
+ if (value.startsWith("#")) {
79
+ const tagValue = value.slice(1).trim();
80
+ return tagValue ? { kind: "tag", value: tagValue } : "selector tag value is required.";
81
+ }
82
+
83
+ const tagMatch = /^tag\s*:\s*(.+)$/i.exec(value);
84
+ if (tagMatch) {
85
+ const tagValue = tagMatch[1].trim();
86
+ return tagValue ? { kind: "tag", value: tagValue } : "selector tag value is required.";
87
+ }
88
+
89
+ const structuredMatch = /^([A-Za-z][A-Za-z0-9_-]*)\s*=\s*(.+)$/.exec(value);
90
+ if (structuredMatch) {
91
+ const key = structuredMatch[1].trim();
92
+ const attributeValue = structuredMatch[2].trim();
93
+ return key && attributeValue
94
+ ? { kind: "structured", key, value: attributeValue }
95
+ : "selector key and value are required.";
96
+ }
97
+
98
+ return `selector "${value}" is ambiguous. Use "group=${value}" for structured group matching or "tag:${value}" for USER.md tags.`;
99
+ }
100
+
71
101
  function normalizeUserAttributeMatch(params: SendUserAttributeToolParams): UserAttributeMatch | string {
102
+ if (params.selector !== undefined) {
103
+ return normalizeUserAttributeSelector(params.selector);
104
+ }
105
+
72
106
  const match = params.match;
73
107
  if (!match || typeof match !== "object") {
74
- return "match is required.";
108
+ return "selector is required.";
75
109
  }
76
110
 
77
111
  if (match.kind === "tag") {
@@ -452,14 +486,20 @@ export function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter) {
452
486
  name: "p2p_send_user_attribute_message",
453
487
  label: "P2P Send User Attribute Message",
454
488
  description:
455
- "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.",
489
+ 'Send a user message to discovered OpenClaw instances matching a public user attribute selector. Use selectors like "group=实验室", "project=小龙虾", "tag:P2P", or "#P2P". Always run a dry run with dryRun=true first and ask the user to confirm targets before group sending.',
456
490
  parameters: {
457
491
  type: "object" as const,
458
492
  properties: {
493
+ selector: {
494
+ type: "string" as const,
495
+ description:
496
+ 'Public attribute selector. Use "key=value" for structured profile attributes such as "group=实验室" or "project=小龙虾"; use "tag:value" or "#value" for USER.md tags. Bare values are rejected as ambiguous.',
497
+ },
459
498
  match: {
460
499
  type: "object" as const,
500
+ deprecated: true,
461
501
  description:
462
- 'User public attribute match. Use { "kind": "tag", "value": "..." } for USER.md tags or { "kind": "structured", "key": "...", "value": "..." } for profile attributes.',
502
+ 'Deprecated compatibility field. Prefer selector. Use { "kind": "tag", "value": "..." } for USER.md tags or { "kind": "structured", "key": "...", "value": "..." } for profile attributes.',
463
503
  oneOf: [
464
504
  {
465
505
  type: "object" as const,
@@ -500,7 +540,7 @@ export function buildP2PTools(mesh: MeshNetwork, router?: InstanceRouter) {
500
540
  description: "Preview matching instances without sending. Run this before group sending.",
501
541
  },
502
542
  },
503
- required: ["match", "message"],
543
+ required: ["selector", "message"],
504
544
  },
505
545
  async execute(_toolCallId: string, params: SendUserAttributeToolParams) {
506
546
  if (!router) {
@@ -101,11 +101,15 @@ function isTemplateText(value: string): boolean {
101
101
  }
102
102
 
103
103
  function stripMarkdown(line: string): string {
104
+ return stripMarkdownSyntax(line).replace(FIELD_PREFIX_PATTERN, "").trim();
105
+ }
106
+
107
+ function stripMarkdownSyntax(line: string): string {
104
108
  return line
105
- .replace(FIELD_PREFIX_PATTERN, "")
106
109
  .replace(/`([^`]+)`/g, "$1")
107
110
  .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
108
111
  .replace(/[*_~#>]/g, " ")
112
+ .replace(/\s+/g, " ")
109
113
  .trim();
110
114
  }
111
115
 
@@ -162,14 +166,15 @@ function looksLikeSentence(value: string): boolean {
162
166
  }
163
167
 
164
168
  function fieldValue(line: string): { field: string; value: string } | undefined {
165
- const match = line.match(FIELD_LINE_PATTERN);
169
+ const normalized = stripMarkdownSyntax(line);
170
+ const match = normalized.match(FIELD_LINE_PATTERN);
166
171
  if (!match) {
167
172
  return undefined;
168
173
  }
169
174
 
170
175
  return {
171
176
  field: (match[1] ?? "").toLowerCase(),
172
- value: stripMarkdown(match[2] ?? ""),
177
+ value: stripMarkdownSyntax(match[2] ?? ""),
173
178
  };
174
179
  }
175
180