libp2p-mesh 2026.6.14 → 2026.6.16

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
@@ -476,6 +476,18 @@ There are two sources:
476
476
  - `USER.md` tags are extracted read-only at gateway startup. The plugin never edits `USER.md`.
477
477
  - `user-profile.json` stores manually managed structured attributes such as group, project, role, skill, or a custom key.
478
478
 
479
+ By default, `USER.md` is read from:
480
+
481
+ ```text
482
+ ~/.openclaw/workspace/USER.md
483
+ ```
484
+
485
+ When `OPENCLAW_STATE_DIR` is set, the plugin reads:
486
+
487
+ ```text
488
+ $OPENCLAW_STATE_DIR/workspace/USER.md
489
+ ```
490
+
479
491
  Run the profile wizard to manage structured attributes:
480
492
 
481
493
  ```bash
@@ -6,6 +6,7 @@ export type UserMdAttributeSource = {
6
6
  warn?: (message: string) => void;
7
7
  };
8
8
  };
9
+ export declare function resolveUserMdPath(customPath?: string): string;
9
10
  export declare function extractUserMdTags(markdown: string): UserPublicAttribute[];
10
11
  export declare function createUserMdAttributeSource(options?: UserMdAttributeSource): {
11
12
  loadTags(): Promise<UserPublicAttribute[]>;
@@ -1,4 +1,5 @@
1
1
  import { readFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
2
3
  import path from "node:path";
3
4
  import { normalizeAttributeValue } from "./user-attributes.js";
4
5
  const MAX_TAGS = 10;
@@ -8,12 +9,23 @@ const FIELD_LINE_PATTERN = /^(?:[-*]\s*)?(?:#{1,6}\s*)?(name|what to call them|n
8
9
  const TEMPLATE_PATTERN = /\b(?:todo|tbd|n\/a|none|unknown|your name|add notes here|template placeholder|placeholder)\b/i;
9
10
  const EXPLICIT_LIST_SEPARATOR_PATTERN = /[,,、;;|/]/;
10
11
  const ENGLISH_TAG_PATTERN = /^(?:[A-Za-z][A-Za-z0-9]*(?:\.[A-Za-z0-9]+)?|libp2p|node\.js)$/i;
12
+ const TECH_TOKEN_PATTERN = /\b(?:[A-Z][A-Z0-9]{1,}|[A-Za-z]*[0-9][A-Za-z0-9]*|libp2p|node\.js)\b/g;
11
13
  const CHINESE_TAG_PATTERN = /[\p{Script=Han}]{2,8}/gu;
12
14
  const CHINESE_CONTEXT_PATTERNS = [
13
15
  /(?:在|来自|加入|参与|负责)([\p{Script=Han}]{2,8})(?:做|写|用|项目|团队|实验室|方向)?/gu,
14
16
  /(?:做|写|维护|负责|参与)([\p{Script=Han}]{2,8})(?:项目|插件|工具|方向)?/gu,
15
17
  ];
16
18
  const CHINESE_SENTENCE_WORD_PATTERN = /(?:今天|明天|昨天|今晚|晚上|早上|上午|下午|八点|同步|一下|进展|开会|讨论|安排|提醒|需要|已经|可以|应该|我们|你们|他们)/u;
19
+ const CHINESE_STOP_WORDS = new Set([
20
+ "关于",
21
+ "正在",
22
+ "当前",
23
+ "相关",
24
+ "工作",
25
+ "项目",
26
+ "网络",
27
+ "专注",
28
+ ]);
17
29
  const COMMON_WORDS = new Set([
18
30
  "and",
19
31
  "also",
@@ -42,6 +54,27 @@ const ENGLISH_TAG_FIELDS = new Set([
42
54
  "interest",
43
55
  "interests",
44
56
  ]);
57
+ const TECH_TOKEN_FIELDS = new Set([
58
+ "notes",
59
+ "note",
60
+ "context",
61
+ "project",
62
+ "projects",
63
+ "skill",
64
+ "skills",
65
+ "interest",
66
+ "interests",
67
+ ]);
68
+ export function resolveUserMdPath(customPath) {
69
+ if (customPath) {
70
+ return customPath;
71
+ }
72
+ const stateDir = process.env.OPENCLAW_STATE_DIR;
73
+ if (stateDir) {
74
+ return path.join(stateDir, "workspace", "USER.md");
75
+ }
76
+ return path.join(homedir(), ".openclaw", "workspace", "USER.md");
77
+ }
45
78
  function isTemplateText(value) {
46
79
  const trimmed = value.trim();
47
80
  return trimmed.length === 0 || TEMPLATE_PATTERN.test(trimmed) || /^\[[^\]]+\]$/.test(trimmed);
@@ -68,8 +101,19 @@ function trimChineseCandidate(value) {
68
101
  }
69
102
  function isStableChineseCandidate(value) {
70
103
  return (/^[\p{Script=Han}]{2,8}$/u.test(value) &&
104
+ !CHINESE_STOP_WORDS.has(value) &&
71
105
  !CHINESE_SENTENCE_WORD_PATTERN.test(value));
72
106
  }
107
+ function collectTechnicalTokens(value) {
108
+ const tokens = [];
109
+ for (const match of value.matchAll(TECH_TOKEN_PATTERN)) {
110
+ const token = trimCandidate(match[0] ?? "");
111
+ if (isStableEnglishCandidate(token)) {
112
+ tokens.push(token);
113
+ }
114
+ }
115
+ return tokens;
116
+ }
73
117
  function collectChineseCandidates(value) {
74
118
  const candidates = [];
75
119
  for (const match of value.matchAll(CHINESE_TAG_PATTERN)) {
@@ -136,6 +180,9 @@ function collectCandidates(line) {
136
180
  }
137
181
  const explicitField = fieldValue(line);
138
182
  const explicitFieldValue = explicitField?.value;
183
+ if (explicitField && TECH_TOKEN_FIELDS.has(explicitField.field)) {
184
+ candidates.push(...collectTechnicalTokens(explicitField.value));
185
+ }
139
186
  if (/^[\p{Script=Han}]{2,8}$/u.test(text) && isStableChineseCandidate(text)) {
140
187
  candidates.push(text);
141
188
  }
@@ -183,7 +230,7 @@ export function extractUserMdTags(markdown) {
183
230
  return tags;
184
231
  }
185
232
  export function createUserMdAttributeSource(options) {
186
- const filePath = options?.path ?? path.join(process.cwd(), "USER.md");
233
+ const filePath = resolveUserMdPath(options?.path);
187
234
  const logger = options?.logger;
188
235
  return {
189
236
  async loadTags() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libp2p-mesh",
3
- "version": "2026.6.14",
3
+ "version": "2026.6.16",
4
4
  "description": "OpenClaw libp2p P2P mesh network plugin for cross-instance agent communication",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,4 +1,5 @@
1
1
  import { readFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
2
3
  import path from "node:path";
3
4
 
4
5
  import type { UserPublicAttribute } from "./types.js";
@@ -15,6 +16,7 @@ const TEMPLATE_PATTERN =
15
16
  /\b(?:todo|tbd|n\/a|none|unknown|your name|add notes here|template placeholder|placeholder)\b/i;
16
17
  const EXPLICIT_LIST_SEPARATOR_PATTERN = /[,,、;;|/]/;
17
18
  const ENGLISH_TAG_PATTERN = /^(?:[A-Za-z][A-Za-z0-9]*(?:\.[A-Za-z0-9]+)?|libp2p|node\.js)$/i;
19
+ const TECH_TOKEN_PATTERN = /\b(?:[A-Z][A-Z0-9]{1,}|[A-Za-z]*[0-9][A-Za-z0-9]*|libp2p|node\.js)\b/g;
18
20
  const CHINESE_TAG_PATTERN = /[\p{Script=Han}]{2,8}/gu;
19
21
  const CHINESE_CONTEXT_PATTERNS = [
20
22
  /(?:在|来自|加入|参与|负责)([\p{Script=Han}]{2,8})(?:做|写|用|项目|团队|实验室|方向)?/gu,
@@ -22,6 +24,16 @@ const CHINESE_CONTEXT_PATTERNS = [
22
24
  ];
23
25
  const CHINESE_SENTENCE_WORD_PATTERN =
24
26
  /(?:今天|明天|昨天|今晚|晚上|早上|上午|下午|八点|同步|一下|进展|开会|讨论|安排|提醒|需要|已经|可以|应该|我们|你们|他们)/u;
27
+ const CHINESE_STOP_WORDS = new Set([
28
+ "关于",
29
+ "正在",
30
+ "当前",
31
+ "相关",
32
+ "工作",
33
+ "项目",
34
+ "网络",
35
+ "专注",
36
+ ]);
25
37
  const COMMON_WORDS = new Set([
26
38
  "and",
27
39
  "also",
@@ -50,6 +62,17 @@ const ENGLISH_TAG_FIELDS = new Set([
50
62
  "interest",
51
63
  "interests",
52
64
  ]);
65
+ const TECH_TOKEN_FIELDS = new Set([
66
+ "notes",
67
+ "note",
68
+ "context",
69
+ "project",
70
+ "projects",
71
+ "skill",
72
+ "skills",
73
+ "interest",
74
+ "interests",
75
+ ]);
53
76
 
54
77
  export type UserMdAttributeSource = {
55
78
  path?: string;
@@ -59,6 +82,19 @@ export type UserMdAttributeSource = {
59
82
  };
60
83
  };
61
84
 
85
+ export function resolveUserMdPath(customPath?: string): string {
86
+ if (customPath) {
87
+ return customPath;
88
+ }
89
+
90
+ const stateDir = process.env.OPENCLAW_STATE_DIR;
91
+ if (stateDir) {
92
+ return path.join(stateDir, "workspace", "USER.md");
93
+ }
94
+
95
+ return path.join(homedir(), ".openclaw", "workspace", "USER.md");
96
+ }
97
+
62
98
  function isTemplateText(value: string): boolean {
63
99
  const trimmed = value.trim();
64
100
  return trimmed.length === 0 || TEMPLATE_PATTERN.test(trimmed) || /^\[[^\]]+\]$/.test(trimmed);
@@ -90,10 +126,24 @@ function trimChineseCandidate(value: string): string {
90
126
  function isStableChineseCandidate(value: string): boolean {
91
127
  return (
92
128
  /^[\p{Script=Han}]{2,8}$/u.test(value) &&
129
+ !CHINESE_STOP_WORDS.has(value) &&
93
130
  !CHINESE_SENTENCE_WORD_PATTERN.test(value)
94
131
  );
95
132
  }
96
133
 
134
+ function collectTechnicalTokens(value: string): string[] {
135
+ const tokens: string[] = [];
136
+
137
+ for (const match of value.matchAll(TECH_TOKEN_PATTERN)) {
138
+ const token = trimCandidate(match[0] ?? "");
139
+ if (isStableEnglishCandidate(token)) {
140
+ tokens.push(token);
141
+ }
142
+ }
143
+
144
+ return tokens;
145
+ }
146
+
97
147
  function collectChineseCandidates(value: string): string[] {
98
148
  const candidates: string[] = [];
99
149
 
@@ -177,6 +227,10 @@ function collectCandidates(line: string): string[] {
177
227
  const explicitField = fieldValue(line);
178
228
  const explicitFieldValue = explicitField?.value;
179
229
 
230
+ if (explicitField && TECH_TOKEN_FIELDS.has(explicitField.field)) {
231
+ candidates.push(...collectTechnicalTokens(explicitField.value));
232
+ }
233
+
180
234
  if (/^[\p{Script=Han}]{2,8}$/u.test(text) && isStableChineseCandidate(text)) {
181
235
  candidates.push(text);
182
236
  }
@@ -236,7 +290,7 @@ export function extractUserMdTags(markdown: string): UserPublicAttribute[] {
236
290
  export function createUserMdAttributeSource(options?: UserMdAttributeSource): {
237
291
  loadTags(): Promise<UserPublicAttribute[]>;
238
292
  } {
239
- const filePath = options?.path ?? path.join(process.cwd(), "USER.md");
293
+ const filePath = resolveUserMdPath(options?.path);
240
294
  const logger = options?.logger;
241
295
 
242
296
  return {