libp2p-mesh 2026.6.21 → 2026.6.22

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.
@@ -6,6 +6,7 @@ import { createInstanceRouter } from "./instance-router.js";
6
6
  import { createMeshNetwork } from "./mesh.js";
7
7
  import { createPeerLabelStore } from "./peer-label-store.js";
8
8
  import { createUserMdAgentAttributeSource } from "./user-md-agent-attributes.js";
9
+ import { createOpenClawUserMdAttributeExtractor } from "./user-md-openclaw-extractor.js";
9
10
  import { createUserProfileStore } from "./user-profile-store.js";
10
11
  import { buildP2PTools } from "./agent-tools.js";
11
12
  import { registerLibp2pMeshCli } from "./profile-cli.js";
@@ -23,7 +24,10 @@ export function registerLibp2pMeshWithDeps(api, deps = {}) {
23
24
  });
24
25
  const store = createInstancePeerStore({ logger: api.logger });
25
26
  const peerLabelStore = createPeerLabelStore({ logger: api.logger });
26
- const userAttributeSource = createUserMdAgentAttributeSource({ logger: api.logger });
27
+ const userAttributeSource = createUserMdAgentAttributeSource({
28
+ logger: api.logger,
29
+ extractor: createOpenClawUserMdAttributeExtractor(api),
30
+ });
27
31
  const userProfileStore = createUserProfileStore({ logger: api.logger });
28
32
  const delivery = createOpenClawRuntimeInboundDelivery({
29
33
  config: api.config,
@@ -0,0 +1,14 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
2
+ import type { UserPublicAttribute } from "./types.js";
3
+ import type { UserMdAttributeExtractor } from "./user-md-agent-attributes.js";
4
+ export declare const USER_MD_ATTRIBUTE_EXTRACTION_PROMPT: string;
5
+ type Logger = {
6
+ warn?: (message: string) => void;
7
+ };
8
+ export declare function extractLatestAssistantText(messages: unknown[]): string | undefined;
9
+ export declare function parseUserMdAttributeResponse(text: string): UserPublicAttribute[];
10
+ export declare function createOpenClawUserMdAttributeExtractor(api: OpenClawPluginApi, options?: {
11
+ timeoutMs?: number;
12
+ logger?: Logger;
13
+ }): UserMdAttributeExtractor;
14
+ export {};
@@ -0,0 +1,135 @@
1
+ import { createHash } from "node:crypto";
2
+ import { validateExtractedUserMdTags } from "./user-md-agent-attributes.js";
3
+ const EXTRACTION_TIMEOUT_MS = 30000;
4
+ const SESSION_HASH_LENGTH = 16;
5
+ export const USER_MD_ATTRIBUTE_EXTRACTION_PROMPT = [
6
+ "你是 libp2p-mesh 的 USER.md 公开属性提取器。",
7
+ "",
8
+ "任务:从 USER.md 中提取少量适合公开广播的用户 tag。",
9
+ "",
10
+ "规则:",
11
+ "- 只输出 JSON 数组,不要输出解释、Markdown 或代码块。",
12
+ '- 每项必须是:{"kind":"tag","value":"...","label":"...","source":"USER.md"}',
13
+ "- 最多 10 个。",
14
+ "- 每个 value 不超过 40 个字符。",
15
+ "- 不要提取寒暄、联系方式提示、占位内容、完整句子。",
16
+ "- 不要提取“刚认识”“还在了解”“随时告诉我”这类无分类价值内容。",
17
+ "- 优先提取稳定身份、技术方向、项目方向、长期偏好。",
18
+ ].join("\n");
19
+ function hashForSessionKey(value) {
20
+ return createHash("sha256").update(value, "utf8").digest("hex").slice(0, SESSION_HASH_LENGTH);
21
+ }
22
+ function buildUserMdExtractionMessage(markdown, sourcePath) {
23
+ return [
24
+ `sourcePath: ${sourcePath}`,
25
+ "",
26
+ "USER.md:",
27
+ "```md",
28
+ markdown,
29
+ "```",
30
+ "",
31
+ "只输出 JSON 数组。",
32
+ ].join("\n");
33
+ }
34
+ function isRecord(value) {
35
+ return typeof value === "object" && value !== null;
36
+ }
37
+ function extractTextFromContent(content) {
38
+ if (typeof content === "string") {
39
+ return content;
40
+ }
41
+ if (!Array.isArray(content)) {
42
+ return undefined;
43
+ }
44
+ const parts = [];
45
+ for (const item of content) {
46
+ if (typeof item === "string") {
47
+ parts.push(item);
48
+ continue;
49
+ }
50
+ if (!isRecord(item)) {
51
+ continue;
52
+ }
53
+ if (typeof item.text === "string") {
54
+ parts.push(item.text);
55
+ continue;
56
+ }
57
+ if (typeof item.content === "string") {
58
+ parts.push(item.content);
59
+ }
60
+ }
61
+ return parts.length > 0 ? parts.join("") : undefined;
62
+ }
63
+ export function extractLatestAssistantText(messages) {
64
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
65
+ const message = messages[index];
66
+ if (!isRecord(message) || message.role !== "assistant") {
67
+ continue;
68
+ }
69
+ const text = extractTextFromContent(message.content) ??
70
+ (typeof message.text === "string" ? message.text : undefined) ??
71
+ (typeof message.message === "string" ? message.message : undefined);
72
+ if (text?.trim()) {
73
+ return text;
74
+ }
75
+ }
76
+ return undefined;
77
+ }
78
+ function stripJsonFence(text) {
79
+ const trimmed = text.trim();
80
+ const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
81
+ return fenced ? fenced[1].trim() : trimmed;
82
+ }
83
+ export function parseUserMdAttributeResponse(text) {
84
+ try {
85
+ return validateExtractedUserMdTags(JSON.parse(stripJsonFence(text)));
86
+ }
87
+ catch {
88
+ return [];
89
+ }
90
+ }
91
+ function unavailable(reason) {
92
+ return { unavailable: true, reason };
93
+ }
94
+ export function createOpenClawUserMdAttributeExtractor(api, options) {
95
+ const timeoutMs = options?.timeoutMs ?? EXTRACTION_TIMEOUT_MS;
96
+ const logger = options?.logger ?? api.logger;
97
+ return {
98
+ async extract({ markdown, sourcePath }) {
99
+ const subagent = api.runtime?.subagent;
100
+ if (!subagent?.run || !subagent.waitForRun || !subagent.getSessionMessages) {
101
+ return unavailable("OpenClaw subagent runtime is unavailable");
102
+ }
103
+ const sessionKey = `libp2p-mesh:user-md-attributes:${hashForSessionKey(sourcePath)}`;
104
+ try {
105
+ await subagent.deleteSession?.({ sessionKey, deleteTranscript: true }).catch(() => undefined);
106
+ const { runId } = await subagent.run({
107
+ sessionKey,
108
+ message: buildUserMdExtractionMessage(markdown, sourcePath),
109
+ extraSystemPrompt: USER_MD_ATTRIBUTE_EXTRACTION_PROMPT,
110
+ lane: "libp2p-mesh-user-md-attributes",
111
+ lightContext: true,
112
+ deliver: false,
113
+ });
114
+ const waitResult = await subagent.waitForRun({ runId, timeoutMs });
115
+ if (waitResult.status !== "ok") {
116
+ return unavailable(waitResult.error ?? `OpenClaw subagent extraction ${waitResult.status}`);
117
+ }
118
+ const { messages } = await subagent.getSessionMessages({ sessionKey, limit: 10 });
119
+ const text = extractLatestAssistantText(messages);
120
+ if (!text) {
121
+ return [];
122
+ }
123
+ return parseUserMdAttributeResponse(text);
124
+ }
125
+ catch (error) {
126
+ const reason = error instanceof Error ? error.message : String(error);
127
+ logger?.warn?.(`[libp2p-mesh] USER.md agent extraction failed: ${reason}`);
128
+ return unavailable(reason);
129
+ }
130
+ finally {
131
+ await subagent.deleteSession?.({ sessionKey, deleteTranscript: true }).catch(() => undefined);
132
+ }
133
+ },
134
+ };
135
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libp2p-mesh",
3
- "version": "2026.6.21",
3
+ "version": "2026.6.22",
4
4
  "description": "OpenClaw libp2p P2P mesh network plugin for cross-instance agent communication",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/plugin.ts CHANGED
@@ -7,6 +7,7 @@ import { createInstanceRouter } from "./instance-router.js";
7
7
  import { createMeshNetwork } from "./mesh.js";
8
8
  import { createPeerLabelStore } from "./peer-label-store.js";
9
9
  import { createUserMdAgentAttributeSource } from "./user-md-agent-attributes.js";
10
+ import { createOpenClawUserMdAttributeExtractor } from "./user-md-openclaw-extractor.js";
10
11
  import { createUserProfileStore } from "./user-profile-store.js";
11
12
  import { buildP2PTools } from "./agent-tools.js";
12
13
  import { registerLibp2pMeshCli } from "./profile-cli.js";
@@ -36,7 +37,10 @@ export function registerLibp2pMeshWithDeps(
36
37
  });
37
38
  const store = createInstancePeerStore({ logger: api.logger });
38
39
  const peerLabelStore = createPeerLabelStore({ logger: api.logger });
39
- const userAttributeSource = createUserMdAgentAttributeSource({ logger: api.logger });
40
+ const userAttributeSource = createUserMdAgentAttributeSource({
41
+ logger: api.logger,
42
+ extractor: createOpenClawUserMdAttributeExtractor(api),
43
+ });
40
44
  const userProfileStore = createUserProfileStore({ logger: api.logger });
41
45
  const delivery = createOpenClawRuntimeInboundDelivery({
42
46
  config: api.config,
@@ -0,0 +1,171 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
3
+ import type { UserPublicAttribute } from "./types.js";
4
+ import type {
5
+ UserMdAttributeExtractionUnavailable,
6
+ UserMdAttributeExtractor,
7
+ } from "./user-md-agent-attributes.js";
8
+ import { validateExtractedUserMdTags } from "./user-md-agent-attributes.js";
9
+
10
+ const EXTRACTION_TIMEOUT_MS = 30000;
11
+ const SESSION_HASH_LENGTH = 16;
12
+
13
+ export const USER_MD_ATTRIBUTE_EXTRACTION_PROMPT = [
14
+ "你是 libp2p-mesh 的 USER.md 公开属性提取器。",
15
+ "",
16
+ "任务:从 USER.md 中提取少量适合公开广播的用户 tag。",
17
+ "",
18
+ "规则:",
19
+ "- 只输出 JSON 数组,不要输出解释、Markdown 或代码块。",
20
+ '- 每项必须是:{"kind":"tag","value":"...","label":"...","source":"USER.md"}',
21
+ "- 最多 10 个。",
22
+ "- 每个 value 不超过 40 个字符。",
23
+ "- 不要提取寒暄、联系方式提示、占位内容、完整句子。",
24
+ "- 不要提取“刚认识”“还在了解”“随时告诉我”这类无分类价值内容。",
25
+ "- 优先提取稳定身份、技术方向、项目方向、长期偏好。",
26
+ ].join("\n");
27
+
28
+ type Logger = {
29
+ warn?: (message: string) => void;
30
+ };
31
+
32
+ function hashForSessionKey(value: string): string {
33
+ return createHash("sha256").update(value, "utf8").digest("hex").slice(0, SESSION_HASH_LENGTH);
34
+ }
35
+
36
+ function buildUserMdExtractionMessage(markdown: string, sourcePath: string): string {
37
+ return [
38
+ `sourcePath: ${sourcePath}`,
39
+ "",
40
+ "USER.md:",
41
+ "```md",
42
+ markdown,
43
+ "```",
44
+ "",
45
+ "只输出 JSON 数组。",
46
+ ].join("\n");
47
+ }
48
+
49
+ function isRecord(value: unknown): value is Record<string, unknown> {
50
+ return typeof value === "object" && value !== null;
51
+ }
52
+
53
+ function extractTextFromContent(content: unknown): string | undefined {
54
+ if (typeof content === "string") {
55
+ return content;
56
+ }
57
+
58
+ if (!Array.isArray(content)) {
59
+ return undefined;
60
+ }
61
+
62
+ const parts: string[] = [];
63
+ for (const item of content) {
64
+ if (typeof item === "string") {
65
+ parts.push(item);
66
+ continue;
67
+ }
68
+ if (!isRecord(item)) {
69
+ continue;
70
+ }
71
+ if (typeof item.text === "string") {
72
+ parts.push(item.text);
73
+ continue;
74
+ }
75
+ if (typeof item.content === "string") {
76
+ parts.push(item.content);
77
+ }
78
+ }
79
+
80
+ return parts.length > 0 ? parts.join("") : undefined;
81
+ }
82
+
83
+ export function extractLatestAssistantText(messages: unknown[]): string | undefined {
84
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
85
+ const message = messages[index];
86
+ if (!isRecord(message) || message.role !== "assistant") {
87
+ continue;
88
+ }
89
+
90
+ const text =
91
+ extractTextFromContent(message.content) ??
92
+ (typeof message.text === "string" ? message.text : undefined) ??
93
+ (typeof message.message === "string" ? message.message : undefined);
94
+ if (text?.trim()) {
95
+ return text;
96
+ }
97
+ }
98
+
99
+ return undefined;
100
+ }
101
+
102
+ function stripJsonFence(text: string): string {
103
+ const trimmed = text.trim();
104
+ const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
105
+ return fenced ? fenced[1].trim() : trimmed;
106
+ }
107
+
108
+ export function parseUserMdAttributeResponse(text: string): UserPublicAttribute[] {
109
+ try {
110
+ return validateExtractedUserMdTags(JSON.parse(stripJsonFence(text)));
111
+ } catch {
112
+ return [];
113
+ }
114
+ }
115
+
116
+ function unavailable(reason: string): UserMdAttributeExtractionUnavailable {
117
+ return { unavailable: true, reason };
118
+ }
119
+
120
+ export function createOpenClawUserMdAttributeExtractor(
121
+ api: OpenClawPluginApi,
122
+ options?: {
123
+ timeoutMs?: number;
124
+ logger?: Logger;
125
+ },
126
+ ): UserMdAttributeExtractor {
127
+ const timeoutMs = options?.timeoutMs ?? EXTRACTION_TIMEOUT_MS;
128
+ const logger = options?.logger ?? api.logger;
129
+
130
+ return {
131
+ async extract({ markdown, sourcePath }) {
132
+ const subagent = api.runtime?.subagent;
133
+ if (!subagent?.run || !subagent.waitForRun || !subagent.getSessionMessages) {
134
+ return unavailable("OpenClaw subagent runtime is unavailable");
135
+ }
136
+
137
+ const sessionKey = `libp2p-mesh:user-md-attributes:${hashForSessionKey(sourcePath)}`;
138
+
139
+ try {
140
+ await subagent.deleteSession?.({ sessionKey, deleteTranscript: true }).catch(() => undefined);
141
+ const { runId } = await subagent.run({
142
+ sessionKey,
143
+ message: buildUserMdExtractionMessage(markdown, sourcePath),
144
+ extraSystemPrompt: USER_MD_ATTRIBUTE_EXTRACTION_PROMPT,
145
+ lane: "libp2p-mesh-user-md-attributes",
146
+ lightContext: true,
147
+ deliver: false,
148
+ });
149
+
150
+ const waitResult = await subagent.waitForRun({ runId, timeoutMs });
151
+ if (waitResult.status !== "ok") {
152
+ return unavailable(waitResult.error ?? `OpenClaw subagent extraction ${waitResult.status}`);
153
+ }
154
+
155
+ const { messages } = await subagent.getSessionMessages({ sessionKey, limit: 10 });
156
+ const text = extractLatestAssistantText(messages);
157
+ if (!text) {
158
+ return [];
159
+ }
160
+
161
+ return parseUserMdAttributeResponse(text);
162
+ } catch (error) {
163
+ const reason = error instanceof Error ? error.message : String(error);
164
+ logger?.warn?.(`[libp2p-mesh] USER.md agent extraction failed: ${reason}`);
165
+ return unavailable(reason);
166
+ } finally {
167
+ await subagent.deleteSession?.({ sessionKey, deleteTranscript: true }).catch(() => undefined);
168
+ }
169
+ },
170
+ };
171
+ }