libp2p-mesh 2026.6.12 → 2026.6.13
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 +124 -0
- package/dist/src/agent-tools.d.ts +120 -1
- package/dist/src/agent-tools.js +153 -0
- package/dist/src/instance-peer-store.js +35 -1
- package/dist/src/instance-router.d.ts +2 -8
- package/dist/src/instance-router.js +94 -2
- package/dist/src/plugin.js +8 -2
- package/dist/src/profile-cli.d.ts +27 -0
- package/dist/src/profile-cli.js +47 -0
- package/dist/src/profile-wizard.d.ts +20 -0
- package/dist/src/profile-wizard.js +141 -0
- package/dist/src/setup-cli.d.ts +19 -0
- package/dist/src/setup-cli.js +32 -28
- package/dist/src/types.d.ts +63 -0
- package/dist/src/user-attributes.d.ts +6 -0
- package/dist/src/user-attributes.js +92 -0
- package/dist/src/user-md-attributes.d.ts +12 -0
- package/dist/src/user-md-attributes.js +202 -0
- package/dist/src/user-profile-store.d.ts +25 -0
- package/dist/src/user-profile-store.js +187 -0
- package/openclaw.plugin.json +2 -1
- package/package.json +1 -1
- package/src/agent-tools.ts +187 -1
- package/src/instance-peer-store.ts +41 -1
- package/src/instance-router.ts +121 -12
- package/src/plugin.ts +8 -2
- package/src/profile-cli.ts +85 -0
- package/src/profile-wizard.ts +204 -0
- package/src/setup-cli.ts +40 -29
- package/src/types.ts +68 -0
- package/src/user-attributes.ts +122 -0
- package/src/user-md-attributes.ts +256 -0
- package/src/user-profile-store.ts +259 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { matchesUserAttribute, mergeUserPublicAttributes } from "./user-attributes.js";
|
|
1
2
|
const MAX_DELIVERY_CACHE_ENTRIES = 1000;
|
|
2
3
|
function parsePayload(msg) {
|
|
3
4
|
try {
|
|
@@ -13,6 +14,12 @@ function isNonEmptyString(value) {
|
|
|
13
14
|
function summarizeError(error) {
|
|
14
15
|
return error instanceof Error ? error.message : String(error);
|
|
15
16
|
}
|
|
17
|
+
function describeAttributeMatch(match) {
|
|
18
|
+
if (match.kind === "tag") {
|
|
19
|
+
return `tag ${match.value}`;
|
|
20
|
+
}
|
|
21
|
+
return `structured attribute ${match.key}=${match.value}`;
|
|
22
|
+
}
|
|
16
23
|
function displayTargetId(target) {
|
|
17
24
|
const id = typeof target.id === "string" ? target.id.trim() : "";
|
|
18
25
|
return id && id.length > 0 ? id : undefined;
|
|
@@ -86,7 +93,20 @@ export function createInstanceRouter(options) {
|
|
|
86
93
|
}
|
|
87
94
|
return identity.id;
|
|
88
95
|
}
|
|
89
|
-
function
|
|
96
|
+
async function loadUserPublicAttributes() {
|
|
97
|
+
const [userMdTags, profileAttributes] = await Promise.all([
|
|
98
|
+
options.userAttributeSource?.loadTags().catch((error) => {
|
|
99
|
+
logger?.warn?.(`[libp2p-mesh] Failed to load USER.md public attributes: ${summarizeError(error)}`);
|
|
100
|
+
return [];
|
|
101
|
+
}) ?? Promise.resolve([]),
|
|
102
|
+
options.userProfileStore?.listAttributes().catch((error) => {
|
|
103
|
+
logger?.warn?.(`[libp2p-mesh] Failed to load profile public attributes: ${summarizeError(error)}`);
|
|
104
|
+
return [];
|
|
105
|
+
}) ?? Promise.resolve([]),
|
|
106
|
+
]);
|
|
107
|
+
return mergeUserPublicAttributes(userMdTags, profileAttributes);
|
|
108
|
+
}
|
|
109
|
+
async function buildAnnouncePayload() {
|
|
90
110
|
const identity = mesh.getInstanceIdentity();
|
|
91
111
|
if (!identity) {
|
|
92
112
|
throw new Error("Local instance identity is not initialized");
|
|
@@ -97,6 +117,7 @@ export function createInstanceRouter(options) {
|
|
|
97
117
|
instanceName: identity.name,
|
|
98
118
|
multiaddrs: mesh.getMultiaddrs(),
|
|
99
119
|
pubkey: identity.pubkey,
|
|
120
|
+
userPublicAttributes: await loadUserPublicAttributes(),
|
|
100
121
|
announcedAt: Date.now(),
|
|
101
122
|
};
|
|
102
123
|
}
|
|
@@ -104,7 +125,7 @@ export function createInstanceRouter(options) {
|
|
|
104
125
|
if (!isNonEmptyString(peerId) || peerId === mesh.getLocalPeerId()) {
|
|
105
126
|
return;
|
|
106
127
|
}
|
|
107
|
-
const payload = buildAnnouncePayload();
|
|
128
|
+
const payload = await buildAnnouncePayload();
|
|
108
129
|
await mesh.sendStructuredMessage(peerId, {
|
|
109
130
|
id: crypto.randomUUID(),
|
|
110
131
|
type: "instance-announce",
|
|
@@ -413,6 +434,76 @@ export function createInstanceRouter(options) {
|
|
|
413
434
|
error: ack.error,
|
|
414
435
|
};
|
|
415
436
|
}
|
|
437
|
+
async function resolveUserAttributeTargets(match) {
|
|
438
|
+
const records = await store.list();
|
|
439
|
+
const targets = [];
|
|
440
|
+
for (const record of records) {
|
|
441
|
+
const matchedAttribute = record.userPublicAttributes?.find((attribute) => matchesUserAttribute(attribute, match));
|
|
442
|
+
if (!matchedAttribute) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
targets.push({
|
|
446
|
+
instanceId: record.instanceId,
|
|
447
|
+
instanceName: record.instanceName,
|
|
448
|
+
peerId: record.peerId,
|
|
449
|
+
matchedAttribute,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
return targets;
|
|
453
|
+
}
|
|
454
|
+
async function sendUserAttributeMessage(match, message, sendOptions = {}) {
|
|
455
|
+
const targets = await resolveUserAttributeTargets(match);
|
|
456
|
+
if (targets.length === 0) {
|
|
457
|
+
return {
|
|
458
|
+
matched: 0,
|
|
459
|
+
sent: 0,
|
|
460
|
+
delivered: 0,
|
|
461
|
+
failed: 0,
|
|
462
|
+
error: `No discovered instances match ${describeAttributeMatch(match)}.`,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
if (sendOptions.dryRun === true) {
|
|
466
|
+
return {
|
|
467
|
+
matched: targets.length,
|
|
468
|
+
sent: 0,
|
|
469
|
+
delivered: 0,
|
|
470
|
+
failed: 0,
|
|
471
|
+
targets,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
const results = [];
|
|
475
|
+
for (const target of targets) {
|
|
476
|
+
try {
|
|
477
|
+
const result = await sendInstanceMessage(target.instanceId, message);
|
|
478
|
+
const targetResult = {
|
|
479
|
+
...target,
|
|
480
|
+
sent: result.sent,
|
|
481
|
+
delivered: result.delivered,
|
|
482
|
+
};
|
|
483
|
+
if (result.error) {
|
|
484
|
+
results.push({ ...targetResult, error: result.error });
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
results.push(targetResult);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
results.push({
|
|
492
|
+
...target,
|
|
493
|
+
sent: false,
|
|
494
|
+
delivered: false,
|
|
495
|
+
error: summarizeError(error),
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
return {
|
|
500
|
+
matched: targets.length,
|
|
501
|
+
sent: results.filter((result) => result.sent).length,
|
|
502
|
+
delivered: results.filter((result) => result.delivered).length,
|
|
503
|
+
failed: results.filter((result) => !result.delivered).length,
|
|
504
|
+
results,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
416
507
|
return {
|
|
417
508
|
start,
|
|
418
509
|
stop,
|
|
@@ -421,5 +512,6 @@ export function createInstanceRouter(options) {
|
|
|
421
512
|
listInstances,
|
|
422
513
|
resolveInstance,
|
|
423
514
|
sendInstanceMessage,
|
|
515
|
+
sendUserAttributeMessage,
|
|
424
516
|
};
|
|
425
517
|
}
|
package/dist/src/plugin.js
CHANGED
|
@@ -4,10 +4,12 @@ import { createOpenClawRuntimeInboundDelivery } from "./inbound-delivery.js";
|
|
|
4
4
|
import { createInstancePeerStore } from "./instance-peer-store.js";
|
|
5
5
|
import { createInstanceRouter } from "./instance-router.js";
|
|
6
6
|
import { createMeshNetwork } from "./mesh.js";
|
|
7
|
+
import { createUserMdAttributeSource } from "./user-md-attributes.js";
|
|
8
|
+
import { createUserProfileStore } from "./user-profile-store.js";
|
|
7
9
|
import { buildP2PTools } from "./agent-tools.js";
|
|
8
|
-
import {
|
|
10
|
+
import { registerLibp2pMeshCli } from "./profile-cli.js";
|
|
9
11
|
export function registerLibp2pMesh(api) {
|
|
10
|
-
|
|
12
|
+
registerLibp2pMeshCli(api);
|
|
11
13
|
const config = api.pluginConfig;
|
|
12
14
|
let unsubscribeInbound;
|
|
13
15
|
let serviceStarted = false;
|
|
@@ -16,6 +18,8 @@ export function registerLibp2pMesh(api) {
|
|
|
16
18
|
logger: api.logger,
|
|
17
19
|
});
|
|
18
20
|
const store = createInstancePeerStore({ logger: api.logger });
|
|
21
|
+
const userAttributeSource = createUserMdAttributeSource({ logger: api.logger });
|
|
22
|
+
const userProfileStore = createUserProfileStore({ logger: api.logger });
|
|
19
23
|
const delivery = createOpenClawRuntimeInboundDelivery({
|
|
20
24
|
config: api.config,
|
|
21
25
|
loadAdapter: async (channelId) => {
|
|
@@ -34,6 +38,8 @@ export function registerLibp2pMesh(api) {
|
|
|
34
38
|
delivery,
|
|
35
39
|
config,
|
|
36
40
|
logger: api.logger,
|
|
41
|
+
userAttributeSource,
|
|
42
|
+
userProfileStore,
|
|
37
43
|
});
|
|
38
44
|
const channel = createLibp2pMeshChannel(mesh);
|
|
39
45
|
// 1. Register Service (manages libp2p node lifecycle)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
2
|
+
import type { OpenClawPluginCliContext } from "openclaw/plugin-sdk/plugin-runtime";
|
|
3
|
+
import { type SetupCliDeps } from "./setup-cli.js";
|
|
4
|
+
import { type UserProfileStore } from "./user-profile-store.js";
|
|
5
|
+
import type { SetupPrompter } from "./setup-wizard.js";
|
|
6
|
+
type CliRootCommand = {
|
|
7
|
+
command(name: string): {
|
|
8
|
+
description(text: string): {
|
|
9
|
+
action(handler: () => Promise<void>): void;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
export type ProfileCliDeps = {
|
|
14
|
+
createPrompter?: (ctx: OpenClawPluginCliContext) => SetupPrompter;
|
|
15
|
+
createProfileStore?: (api: OpenClawPluginApi) => Pick<UserProfileStore, "listAttributes" | "replaceAttributes">;
|
|
16
|
+
createUserMdAttributeSource?: (api: OpenClawPluginApi) => {
|
|
17
|
+
loadTags(): Promise<Awaited<ReturnType<UserProfileStore["listAttributes"]>>>;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export type Libp2pMeshCliDeps = {
|
|
21
|
+
setup?: SetupCliDeps;
|
|
22
|
+
profile?: ProfileCliDeps;
|
|
23
|
+
};
|
|
24
|
+
export declare function registerLibp2pMeshCli(api: OpenClawPluginApi, deps?: Libp2pMeshCliDeps): void;
|
|
25
|
+
export declare function registerLibp2pMeshProfileCli(api: OpenClawPluginApi, deps?: ProfileCliDeps): void;
|
|
26
|
+
export declare function registerLibp2pMeshProfileCommand(root: CliRootCommand, api: OpenClawPluginApi, ctx: OpenClawPluginCliContext, deps?: ProfileCliDeps): void;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { createReadlinePrompter, LIBP2P_MESH_CLI_REGISTRATION, registerLibp2pMeshSetupCommand, } from "./setup-cli.js";
|
|
2
|
+
import { runProfileWizard } from "./profile-wizard.js";
|
|
3
|
+
import { createUserMdAttributeSource } from "./user-md-attributes.js";
|
|
4
|
+
import { createUserProfileStore } from "./user-profile-store.js";
|
|
5
|
+
export function registerLibp2pMeshCli(api, deps = {}) {
|
|
6
|
+
api.registerCli((ctx) => {
|
|
7
|
+
const root = ctx.program
|
|
8
|
+
.command("libp2p-mesh")
|
|
9
|
+
.description("Configure libp2p-mesh plugin.");
|
|
10
|
+
registerLibp2pMeshSetupCommand(root, api, ctx, deps.setup);
|
|
11
|
+
registerLibp2pMeshProfileCommand(root, api, ctx, deps.profile);
|
|
12
|
+
}, LIBP2P_MESH_CLI_REGISTRATION);
|
|
13
|
+
}
|
|
14
|
+
export function registerLibp2pMeshProfileCli(api, deps = {}) {
|
|
15
|
+
api.registerCli((ctx) => {
|
|
16
|
+
const root = ctx.program
|
|
17
|
+
.command("libp2p-mesh")
|
|
18
|
+
.description("Configure libp2p-mesh plugin.");
|
|
19
|
+
registerLibp2pMeshProfileCommand(root, api, ctx, deps);
|
|
20
|
+
}, LIBP2P_MESH_CLI_REGISTRATION);
|
|
21
|
+
}
|
|
22
|
+
export function registerLibp2pMeshProfileCommand(root, api, ctx, deps = {}) {
|
|
23
|
+
root
|
|
24
|
+
.command("profile")
|
|
25
|
+
.description("Manage libp2p-mesh public profile attributes.")
|
|
26
|
+
.action(async () => {
|
|
27
|
+
const prompter = (deps.createPrompter?.(ctx) ?? createReadlinePrompter());
|
|
28
|
+
const profileStore = deps.createProfileStore?.(api) ?? createUserProfileStore({ logger: api.logger });
|
|
29
|
+
const userMdAttributeSource = deps.createUserMdAttributeSource?.(api) ?? createUserMdAttributeSource({ logger: api.logger });
|
|
30
|
+
try {
|
|
31
|
+
const result = await runProfileWizard({
|
|
32
|
+
prompter,
|
|
33
|
+
readOnlyTags: await userMdAttributeSource.loadTags(),
|
|
34
|
+
profileAttributes: await profileStore.listAttributes(),
|
|
35
|
+
writer: {
|
|
36
|
+
async replaceAttributes(attributes) {
|
|
37
|
+
await profileStore.replaceAttributes(attributes);
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
prompter.print(result.message);
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
prompter.close?.();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type SetupPrompter } from "./setup-wizard.js";
|
|
2
|
+
import type { UserPublicAttribute } from "./types.js";
|
|
3
|
+
export type UserProfileWriter = {
|
|
4
|
+
replaceAttributes(attributes: UserPublicAttribute[]): Promise<void>;
|
|
5
|
+
};
|
|
6
|
+
export type RunProfileWizardOptions = {
|
|
7
|
+
prompter: SetupPrompter;
|
|
8
|
+
readOnlyTags: UserPublicAttribute[];
|
|
9
|
+
profileAttributes: UserPublicAttribute[];
|
|
10
|
+
writer: UserProfileWriter;
|
|
11
|
+
};
|
|
12
|
+
export type ProfileWizardResult = {
|
|
13
|
+
status: "saved";
|
|
14
|
+
attributes: UserPublicAttribute[];
|
|
15
|
+
message: string;
|
|
16
|
+
} | {
|
|
17
|
+
status: "cancelled";
|
|
18
|
+
message: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function runProfileWizard(options: RunProfileWizardOptions): Promise<ProfileWizardResult>;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { SetupCancelledError } from "./setup-wizard.js";
|
|
2
|
+
import { mergeUserPublicAttributes, normalizeAttributeKey, normalizeUserPublicAttribute, } from "./user-attributes.js";
|
|
3
|
+
const CANCELLED_MESSAGE = "Profile update cancelled. No changes were written.";
|
|
4
|
+
const SAVED_MESSAGE = "Profile attributes saved.\n\nRestart the gateway to broadcast updated attributes.";
|
|
5
|
+
export async function runProfileWizard(options) {
|
|
6
|
+
try {
|
|
7
|
+
const readOnlyTags = options.readOnlyTags
|
|
8
|
+
.map((attribute) => normalizeUserPublicAttribute(attribute))
|
|
9
|
+
.filter((attribute) => attribute?.kind === "tag");
|
|
10
|
+
let attributes = normalizeProfileAttributes(options.profileAttributes);
|
|
11
|
+
options.prompter.print(formatProfileOverview(readOnlyTags, attributes));
|
|
12
|
+
while (true) {
|
|
13
|
+
const action = await options.prompter.select("What do you want to do?", [
|
|
14
|
+
{ label: "Add structured attribute", value: "add-attribute" },
|
|
15
|
+
{ label: "Edit structured attribute", value: "edit-attribute" },
|
|
16
|
+
{ label: "Remove structured attribute", value: "remove-attribute" },
|
|
17
|
+
{ label: "Preview and finish", value: "preview-finish" },
|
|
18
|
+
{ label: "Cancel", value: "cancel" },
|
|
19
|
+
]);
|
|
20
|
+
switch (action) {
|
|
21
|
+
case "add-attribute":
|
|
22
|
+
attributes = mergeUserPublicAttributes([], [...attributes, await promptForAttribute(options.prompter)]);
|
|
23
|
+
options.prompter.print(formatProfileOverview(readOnlyTags, attributes));
|
|
24
|
+
break;
|
|
25
|
+
case "edit-attribute":
|
|
26
|
+
attributes = await promptForAttributeEdit(options.prompter, attributes);
|
|
27
|
+
options.prompter.print(formatProfileOverview(readOnlyTags, attributes));
|
|
28
|
+
break;
|
|
29
|
+
case "remove-attribute":
|
|
30
|
+
attributes = await promptForAttributeRemoval(options.prompter, attributes);
|
|
31
|
+
options.prompter.print(formatProfileOverview(readOnlyTags, attributes));
|
|
32
|
+
break;
|
|
33
|
+
case "preview-finish":
|
|
34
|
+
options.prompter.print(formatProfilePreview(readOnlyTags, attributes));
|
|
35
|
+
if (!(await options.prompter.confirm("Save profile attributes?", true))) {
|
|
36
|
+
return cancelledResult();
|
|
37
|
+
}
|
|
38
|
+
await options.writer.replaceAttributes(attributes);
|
|
39
|
+
return {
|
|
40
|
+
status: "saved",
|
|
41
|
+
attributes,
|
|
42
|
+
message: SAVED_MESSAGE,
|
|
43
|
+
};
|
|
44
|
+
case "cancel":
|
|
45
|
+
return cancelledResult();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (error instanceof SetupCancelledError) {
|
|
51
|
+
return cancelledResult();
|
|
52
|
+
}
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function normalizeProfileAttributes(attributes) {
|
|
57
|
+
return mergeUserPublicAttributes([], attributes).filter((attribute) => attribute.kind === "structured");
|
|
58
|
+
}
|
|
59
|
+
async function promptForAttribute(prompter) {
|
|
60
|
+
const category = await prompter.select("Attribute category", [
|
|
61
|
+
{ label: "Group", value: "group" },
|
|
62
|
+
{ label: "Project", value: "project" },
|
|
63
|
+
{ label: "Role", value: "role" },
|
|
64
|
+
{ label: "Skill", value: "skill" },
|
|
65
|
+
{ label: "Custom key", value: "custom" },
|
|
66
|
+
]);
|
|
67
|
+
const key = category === "custom"
|
|
68
|
+
? normalizeAttributeKey(await prompter.input("Custom key", { required: true }))
|
|
69
|
+
: category;
|
|
70
|
+
const value = await prompter.input("Attribute value", { required: true });
|
|
71
|
+
return {
|
|
72
|
+
kind: "structured",
|
|
73
|
+
key,
|
|
74
|
+
value: value.trim(),
|
|
75
|
+
label: `${key}: ${value.trim()}`,
|
|
76
|
+
source: "profile",
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function promptForAttributeEdit(prompter, attributes) {
|
|
80
|
+
if (attributes.length === 0) {
|
|
81
|
+
prompter.print("No structured profile attributes configured.");
|
|
82
|
+
return attributes;
|
|
83
|
+
}
|
|
84
|
+
const selectedIndex = await selectAttributeIndex(prompter, "Attribute to edit", attributes);
|
|
85
|
+
const nextAttribute = await promptForAttribute(prompter);
|
|
86
|
+
return mergeUserPublicAttributes([], attributes.map((attribute, index) => (index === selectedIndex ? nextAttribute : attribute))).filter((attribute) => attribute.kind === "structured");
|
|
87
|
+
}
|
|
88
|
+
async function promptForAttributeRemoval(prompter, attributes) {
|
|
89
|
+
if (attributes.length === 0) {
|
|
90
|
+
prompter.print("No structured profile attributes configured.");
|
|
91
|
+
return attributes;
|
|
92
|
+
}
|
|
93
|
+
const selectedIndex = await selectAttributeIndex(prompter, "Attribute to remove", attributes);
|
|
94
|
+
return attributes.filter((_attribute, index) => index !== selectedIndex);
|
|
95
|
+
}
|
|
96
|
+
async function selectAttributeIndex(prompter, message, attributes) {
|
|
97
|
+
const selectedKey = await prompter.select(message, attributes.map((attribute, index) => ({
|
|
98
|
+
label: formatAttribute(attribute),
|
|
99
|
+
value: `attribute-index-${index}`,
|
|
100
|
+
})));
|
|
101
|
+
const match = /^attribute-index-(\d+)$/.exec(selectedKey);
|
|
102
|
+
return match ? Number(match[1]) : -1;
|
|
103
|
+
}
|
|
104
|
+
function formatProfileOverview(readOnlyTags, attributes) {
|
|
105
|
+
return [
|
|
106
|
+
"Read-only USER.md tags:",
|
|
107
|
+
...formatAttributeList(readOnlyTags),
|
|
108
|
+
"",
|
|
109
|
+
"Structured profile attributes:",
|
|
110
|
+
...formatAttributeList(attributes),
|
|
111
|
+
].join("\n");
|
|
112
|
+
}
|
|
113
|
+
function formatProfilePreview(readOnlyTags, attributes) {
|
|
114
|
+
return [
|
|
115
|
+
"Preview: public attributes",
|
|
116
|
+
"",
|
|
117
|
+
"Read-only USER.md tags:",
|
|
118
|
+
...formatAttributeList(readOnlyTags),
|
|
119
|
+
"",
|
|
120
|
+
"Structured profile attributes to save:",
|
|
121
|
+
...formatAttributeList(attributes),
|
|
122
|
+
].join("\n");
|
|
123
|
+
}
|
|
124
|
+
function formatAttributeList(attributes) {
|
|
125
|
+
if (attributes.length === 0) {
|
|
126
|
+
return [" none"];
|
|
127
|
+
}
|
|
128
|
+
return attributes.map((attribute, index) => ` ${index + 1}. ${formatAttribute(attribute)}`);
|
|
129
|
+
}
|
|
130
|
+
function formatAttribute(attribute) {
|
|
131
|
+
if (attribute.kind === "tag") {
|
|
132
|
+
return `${attribute.label} (USER.md tag, read-only)`;
|
|
133
|
+
}
|
|
134
|
+
return `${attribute.key}: ${attribute.value}`;
|
|
135
|
+
}
|
|
136
|
+
function cancelledResult() {
|
|
137
|
+
return {
|
|
138
|
+
status: "cancelled",
|
|
139
|
+
message: CANCELLED_MESSAGE,
|
|
140
|
+
};
|
|
141
|
+
}
|
package/dist/src/setup-cli.d.ts
CHANGED
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
2
2
|
import type { OpenClawPluginCliContext } from "openclaw/plugin-sdk/plugin-runtime";
|
|
3
3
|
import { type SetupConfigWriter, type SetupPrompter } from "./setup-wizard.js";
|
|
4
|
+
export declare const LIBP2P_MESH_CLI_REGISTRATION: {
|
|
5
|
+
commands: string[];
|
|
6
|
+
descriptors: {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
hasSubcommands: boolean;
|
|
10
|
+
}[];
|
|
11
|
+
};
|
|
12
|
+
export type ClosableSetupPrompter = SetupPrompter & {
|
|
13
|
+
close?: () => void;
|
|
14
|
+
};
|
|
4
15
|
export type SetupCliDeps = {
|
|
5
16
|
createPrompter?: (ctx: OpenClawPluginCliContext) => SetupPrompter;
|
|
6
17
|
createWriter?: (api: OpenClawPluginApi) => SetupConfigWriter;
|
|
7
18
|
};
|
|
8
19
|
export declare function registerLibp2pMeshSetupCli(api: OpenClawPluginApi, deps?: SetupCliDeps): void;
|
|
20
|
+
export declare function registerLibp2pMeshSetupCommand(root: {
|
|
21
|
+
command(name: string): {
|
|
22
|
+
description(text: string): {
|
|
23
|
+
action(handler: () => Promise<void>): void;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
}, api: OpenClawPluginApi, ctx: OpenClawPluginCliContext, deps?: SetupCliDeps): void;
|
|
27
|
+
export declare function createReadlinePrompter(): ClosableSetupPrompter;
|
package/dist/src/setup-cli.js
CHANGED
|
@@ -5,38 +5,42 @@ const SETUP_CLI_AFTER_WRITE = {
|
|
|
5
5
|
mode: "none",
|
|
6
6
|
reason: "libp2p-mesh setup completed; restart manually to apply gateway changes.",
|
|
7
7
|
};
|
|
8
|
+
export const LIBP2P_MESH_CLI_REGISTRATION = {
|
|
9
|
+
commands: ["libp2p-mesh"],
|
|
10
|
+
descriptors: [
|
|
11
|
+
{
|
|
12
|
+
name: "libp2p-mesh",
|
|
13
|
+
description: "Configure libp2p-mesh plugin.",
|
|
14
|
+
hasSubcommands: true,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
};
|
|
8
18
|
export function registerLibp2pMeshSetupCli(api, deps = {}) {
|
|
9
19
|
api.registerCli((ctx) => {
|
|
10
20
|
const root = ctx.program
|
|
11
21
|
.command("libp2p-mesh")
|
|
12
22
|
.description("Configure libp2p-mesh plugin.");
|
|
13
|
-
root
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
name: "libp2p-mesh",
|
|
36
|
-
description: "Configure libp2p-mesh plugin.",
|
|
37
|
-
hasSubcommands: true,
|
|
38
|
-
},
|
|
39
|
-
],
|
|
23
|
+
registerLibp2pMeshSetupCommand(root, api, ctx, deps);
|
|
24
|
+
}, LIBP2P_MESH_CLI_REGISTRATION);
|
|
25
|
+
}
|
|
26
|
+
export function registerLibp2pMeshSetupCommand(root, api, ctx, deps = {}) {
|
|
27
|
+
root
|
|
28
|
+
.command("setup")
|
|
29
|
+
.description("Run the libp2p-mesh setup wizard.")
|
|
30
|
+
.action(async () => {
|
|
31
|
+
const prompter = (deps.createPrompter?.(ctx) ?? createReadlinePrompter());
|
|
32
|
+
const writer = deps.createWriter?.(api) ?? createOpenClawConfigWriter(api);
|
|
33
|
+
try {
|
|
34
|
+
const result = await runSetupWizard({
|
|
35
|
+
currentConfig: ctx.config,
|
|
36
|
+
prompter,
|
|
37
|
+
writer,
|
|
38
|
+
});
|
|
39
|
+
prompter.print(result.message);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
prompter.close?.();
|
|
43
|
+
}
|
|
40
44
|
});
|
|
41
45
|
}
|
|
42
46
|
function createOpenClawConfigWriter(api) {
|
|
@@ -57,7 +61,7 @@ function replaceConfig(draft, nextConfig) {
|
|
|
57
61
|
}
|
|
58
62
|
Object.assign(draft, structuredClone(nextConfig));
|
|
59
63
|
}
|
|
60
|
-
function createReadlinePrompter() {
|
|
64
|
+
export function createReadlinePrompter() {
|
|
61
65
|
const readline = createInterface({ input, output });
|
|
62
66
|
return {
|
|
63
67
|
async confirm(message, defaultValue = false) {
|
package/dist/src/types.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export interface InstanceAnnouncePayload {
|
|
|
38
38
|
instanceName?: string;
|
|
39
39
|
multiaddrs: string[];
|
|
40
40
|
pubkey?: string;
|
|
41
|
+
userPublicAttributes?: UserPublicAttribute[];
|
|
41
42
|
announcedAt: number;
|
|
42
43
|
}
|
|
43
44
|
export interface UserMessagePayload {
|
|
@@ -72,12 +73,33 @@ export interface DeliveryAckPayload {
|
|
|
72
73
|
deliveredAt: number;
|
|
73
74
|
error?: string;
|
|
74
75
|
}
|
|
76
|
+
export type UserPublicAttribute = {
|
|
77
|
+
kind: "tag";
|
|
78
|
+
value: string;
|
|
79
|
+
label: string;
|
|
80
|
+
source: "USER.md";
|
|
81
|
+
} | {
|
|
82
|
+
kind: "structured";
|
|
83
|
+
key: "group" | "project" | "role" | "skill" | "custom" | string;
|
|
84
|
+
value: string;
|
|
85
|
+
label: string;
|
|
86
|
+
source: "profile";
|
|
87
|
+
};
|
|
88
|
+
export type UserAttributeMatch = {
|
|
89
|
+
kind: "tag";
|
|
90
|
+
value: string;
|
|
91
|
+
} | {
|
|
92
|
+
kind: "structured";
|
|
93
|
+
key: string;
|
|
94
|
+
value: string;
|
|
95
|
+
};
|
|
75
96
|
export interface InstancePeerRecord {
|
|
76
97
|
instanceId: string;
|
|
77
98
|
peerId: string;
|
|
78
99
|
instanceName?: string;
|
|
79
100
|
multiaddrs: string[];
|
|
80
101
|
pubkey?: string;
|
|
102
|
+
userPublicAttributes?: UserPublicAttribute[];
|
|
81
103
|
lastSeenAt: number;
|
|
82
104
|
lastAnnouncedAt: number;
|
|
83
105
|
source: "announce";
|
|
@@ -97,6 +119,26 @@ export interface InstancePeerStore {
|
|
|
97
119
|
peerIdSharedBy: string[];
|
|
98
120
|
}>;
|
|
99
121
|
}
|
|
122
|
+
export type UserAttributeMessageTarget = {
|
|
123
|
+
instanceId: string;
|
|
124
|
+
instanceName?: string;
|
|
125
|
+
peerId: string;
|
|
126
|
+
matchedAttribute: UserPublicAttribute;
|
|
127
|
+
};
|
|
128
|
+
export type UserAttributeMessageDeliveryResult = UserAttributeMessageTarget & {
|
|
129
|
+
sent: boolean;
|
|
130
|
+
delivered: boolean;
|
|
131
|
+
error?: string;
|
|
132
|
+
};
|
|
133
|
+
export type UserAttributeMessageResult = {
|
|
134
|
+
matched: number;
|
|
135
|
+
sent: number;
|
|
136
|
+
delivered: number;
|
|
137
|
+
failed: number;
|
|
138
|
+
targets?: UserAttributeMessageTarget[];
|
|
139
|
+
results?: UserAttributeMessageDeliveryResult[];
|
|
140
|
+
error?: string;
|
|
141
|
+
};
|
|
100
142
|
export interface InboundDeliveryRequest {
|
|
101
143
|
channel: string;
|
|
102
144
|
target: string;
|
|
@@ -119,6 +161,24 @@ export interface InboundDeliveryResult {
|
|
|
119
161
|
export interface InboundDeliveryAdapter {
|
|
120
162
|
deliver(request: InboundDeliveryRequest): Promise<InboundDeliveryResult>;
|
|
121
163
|
}
|
|
164
|
+
export type InstanceRouterOptions = {
|
|
165
|
+
mesh: MeshNetwork;
|
|
166
|
+
store: InstancePeerStore;
|
|
167
|
+
delivery: InboundDeliveryAdapter;
|
|
168
|
+
config?: MeshConfig;
|
|
169
|
+
logger?: {
|
|
170
|
+
info?: (message: string) => void;
|
|
171
|
+
debug?: (message: string) => void;
|
|
172
|
+
warn?: (message: string) => void;
|
|
173
|
+
error?: (message: string) => void;
|
|
174
|
+
};
|
|
175
|
+
userAttributeSource?: {
|
|
176
|
+
loadTags(): Promise<UserPublicAttribute[]>;
|
|
177
|
+
};
|
|
178
|
+
userProfileStore?: {
|
|
179
|
+
listAttributes(): Promise<UserPublicAttribute[]>;
|
|
180
|
+
};
|
|
181
|
+
};
|
|
122
182
|
export interface InstanceRouter {
|
|
123
183
|
start(): Promise<void>;
|
|
124
184
|
stop(): Promise<void>;
|
|
@@ -137,6 +197,9 @@ export interface InstanceRouter {
|
|
|
137
197
|
deliveryResults?: DeliveryTargetResult[];
|
|
138
198
|
error?: string;
|
|
139
199
|
}>;
|
|
200
|
+
sendUserAttributeMessage(match: UserAttributeMatch, message: string, options?: {
|
|
201
|
+
dryRun?: boolean;
|
|
202
|
+
}): Promise<UserAttributeMessageResult>;
|
|
140
203
|
}
|
|
141
204
|
export interface MeshConfig {
|
|
142
205
|
listenAddrs?: string[];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { UserAttributeMatch, UserPublicAttribute } from "./types.js";
|
|
2
|
+
export declare function normalizeAttributeValue(value: string): string;
|
|
3
|
+
export declare function normalizeAttributeKey(key: string): string;
|
|
4
|
+
export declare function normalizeUserPublicAttribute(value: unknown): UserPublicAttribute | undefined;
|
|
5
|
+
export declare function mergeUserPublicAttributes(userMdTags: UserPublicAttribute[], profileAttributes: UserPublicAttribute[]): UserPublicAttribute[];
|
|
6
|
+
export declare function matchesUserAttribute(attribute: UserPublicAttribute, match: UserAttributeMatch): boolean;
|