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 +4 -4
- package/dist/src/agent-tools.d.ts +14 -0
- package/dist/src/agent-tools.js +36 -4
- package/dist/src/user-md-attributes.js +7 -3
- package/package.json +1 -1
- package/src/agent-tools.ts +44 -4
- package/src/user-md-attributes.ts +8 -3
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
|
-
"
|
|
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
|
-
"
|
|
585
|
+
"selector": "project=openclaw",
|
|
586
586
|
"message": "今晚同步一下进展",
|
|
587
587
|
"dryRun": false
|
|
588
588
|
})
|
|
589
589
|
```
|
|
590
590
|
|
|
591
|
-
Tag matches use
|
|
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
|
-
"
|
|
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";
|
package/dist/src/agent-tools.js
CHANGED
|
@@ -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 "
|
|
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:
|
|
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
|
-
|
|
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: ["
|
|
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
|
|
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:
|
|
141
|
+
value: stripMarkdownSyntax(match[2] ?? ""),
|
|
138
142
|
};
|
|
139
143
|
}
|
|
140
144
|
function isStableEnglishCandidate(value) {
|
package/package.json
CHANGED
package/src/agent-tools.ts
CHANGED
|
@@ -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 "
|
|
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
|
-
|
|
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
|
-
'
|
|
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: ["
|
|
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
|
|
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:
|
|
177
|
+
value: stripMarkdownSyntax(match[2] ?? ""),
|
|
173
178
|
};
|
|
174
179
|
}
|
|
175
180
|
|