libp2p-mesh 2026.6.13 → 2026.6.14
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 +37 -0
- package/dist/src/debug-cli.d.ts +21 -0
- package/dist/src/debug-cli.js +54 -0
- package/dist/src/debug-wizard.d.ts +19 -0
- package/dist/src/debug-wizard.js +41 -0
- package/dist/src/instance-router.js +45 -5
- package/dist/src/plugin.d.ts +7 -0
- package/dist/src/plugin.js +97 -48
- package/dist/src/profile-cli.d.ts +2 -0
- package/dist/src/profile-cli.js +2 -0
- package/dist/src/setup-config.d.ts +4 -1
- package/dist/src/setup-config.js +27 -0
- package/dist/src/types.d.ts +4 -0
- package/openclaw.plugin.json +12 -0
- package/package.json +1 -1
- package/src/debug-cli.ts +93 -0
- package/src/debug-wizard.ts +64 -0
- package/src/instance-router.ts +67 -6
- package/src/plugin.ts +125 -57
- package/src/profile-cli.ts +3 -0
- package/src/setup-config.ts +35 -1
- package/src/types.ts +5 -0
package/README.md
CHANGED
|
@@ -225,11 +225,34 @@ If `inboundTargets` is an empty array, inbound delivery is disabled. If `inbound
|
|
|
225
225
|
| `relayList` | `string[]` | `[]` | Multiaddrs of relays to reserve a slot on |
|
|
226
226
|
| `discoverRelays` | `number` | `0` | Auto-discover this many relays via content routing |
|
|
227
227
|
| `announceAddrs` | `string[]` | `[]` | Extra multiaddrs to announce on top of auto-detected ones |
|
|
228
|
+
| `announceLogDetail` | `"off" \| "summary" \| "payload"` | `"summary"` | Controls instance announce logging. `summary` logs peer, instance, address count, and attribute count; `payload` also logs the full announce JSON; `off` disables only the new announce summary/payload logs and keeps legacy/basic info logs. |
|
|
228
229
|
| `inboundChannel` | `string` | `undefined` | OpenClaw channel used to display inbound P2P user messages, for example `"feishu"` |
|
|
229
230
|
| `inboundTarget` | `string` | `undefined` | OpenClaw channel target for inbound P2P messages, for example `user:ou_xxx` or `chat:oc_xxx` |
|
|
230
231
|
| `inboundTargets` | `array` | `undefined` | Optional list of receiver-owned channel targets for inbound P2P user messages. When present, it overrides `inboundChannel`/`inboundTarget`; an empty array disables inbound delivery. |
|
|
231
232
|
| `deliveryAckTimeoutMs` | `number` | `15000` | Timeout for waiting on remote channel delivery ACKs |
|
|
232
233
|
|
|
234
|
+
### Announce Startup and Logging
|
|
235
|
+
|
|
236
|
+
During gateway startup, `libp2p-mesh` registers the instance router handlers and direct/broadcast inbound handlers before starting the mesh node. This makes early `instance-announce` messages observable as soon as peers connect, instead of waiting until after mesh startup has already completed.
|
|
237
|
+
|
|
238
|
+
Instance announce logs are controlled by `plugins.entries["libp2p-mesh"].config.announceLogDetail`:
|
|
239
|
+
|
|
240
|
+
- `summary` is the default. It logs send/receive direction, peer ID, instance ID, multiaddr count, and public attribute count. It does not print the full announce JSON.
|
|
241
|
+
- `off` disables the new announce summary and payload logs. It still keeps legacy/basic info logs such as sent announce lines and instance mapping updates, along with warnings and errors.
|
|
242
|
+
- `payload` logs the same summary plus the full announce JSON at debug level.
|
|
243
|
+
|
|
244
|
+
Use the debug command to inspect or change this value:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
openclaw libp2p-mesh debug
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Full payload logging is intended for short-lived troubleshooting only. Announce payloads can include `userPublicAttributes`, peer multiaddrs, the instance pubkey, and instance identity fields. After changing the setting, restart the gateway for the new logging level to take effect:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
openclaw gateway restart
|
|
254
|
+
```
|
|
255
|
+
|
|
233
256
|
## NAT Traversal
|
|
234
257
|
|
|
235
258
|
When both peers have a routable address (same LAN, public IPs, or working port-forwarding) no extra setup is needed. The defaults above kick in automatically:
|
|
@@ -616,6 +639,20 @@ Peer connection and disconnection are logged at `info` level:
|
|
|
616
639
|
|
|
617
640
|
If these lines are missing, confirm the gateway is running with normal info logs enabled and that both instances are on the same mDNS, bootstrap, or relay network.
|
|
618
641
|
|
|
642
|
+
### Instance announce routes are missing between two machines
|
|
643
|
+
|
|
644
|
+
If peers connect but sending by OpenClaw instance ID fails or `instance-peer.json` is not updated, first confirm both gateways were restarted after the latest config change. On startup, the gateway now attaches the instance router plus inbound message handlers before starting the mesh, so early announces should be handled once the peer connection appears.
|
|
645
|
+
|
|
646
|
+
For a short debug session on both computers:
|
|
647
|
+
|
|
648
|
+
1. Run `openclaw libp2p-mesh debug`.
|
|
649
|
+
2. Set `announceLogDetail` to `payload` and confirm the privacy warning.
|
|
650
|
+
3. Restart both gateways with `openclaw gateway restart`.
|
|
651
|
+
4. Watch for summary lines and debug lines containing full announce payload JSON.
|
|
652
|
+
5. Return to `summary` or `off` with `openclaw libp2p-mesh debug`, then restart again.
|
|
653
|
+
|
|
654
|
+
Full payload logs may expose `userPublicAttributes`, multiaddrs, pubkey, and instance identity, so avoid sharing these logs outside the debugging context.
|
|
655
|
+
|
|
619
656
|
## Architecture
|
|
620
657
|
|
|
621
658
|
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
2
|
+
import type { OpenClawPluginCliContext } from "openclaw/plugin-sdk/plugin-runtime";
|
|
3
|
+
import type { SetupPrompter } from "./setup-wizard.js";
|
|
4
|
+
import type { AnnounceLogDetail } from "./types.js";
|
|
5
|
+
type CliRootCommand = {
|
|
6
|
+
command(name: string): {
|
|
7
|
+
description(text: string): {
|
|
8
|
+
action(handler: () => Promise<void>): void;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export type DebugConfigWriter = {
|
|
13
|
+
saveAnnounceLogDetail(detail: AnnounceLogDetail): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
export type DebugCliDeps = {
|
|
16
|
+
createPrompter?: (ctx: OpenClawPluginCliContext) => SetupPrompter;
|
|
17
|
+
createWriter?: (api: OpenClawPluginApi) => DebugConfigWriter;
|
|
18
|
+
};
|
|
19
|
+
export declare function registerLibp2pMeshDebugCli(api: OpenClawPluginApi, deps?: DebugCliDeps): void;
|
|
20
|
+
export declare function registerLibp2pMeshDebugCommand(root: CliRootCommand, api: OpenClawPluginApi, ctx: OpenClawPluginCliContext, deps?: DebugCliDeps): void;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createReadlinePrompter, LIBP2P_MESH_CLI_REGISTRATION, } from "./setup-cli.js";
|
|
2
|
+
import { applyAnnounceLogDetail, getAnnounceLogDetail, } from "./setup-config.js";
|
|
3
|
+
import { runDebugWizard } from "./debug-wizard.js";
|
|
4
|
+
const DEBUG_CLI_AFTER_WRITE = {
|
|
5
|
+
mode: "none",
|
|
6
|
+
reason: "libp2p-mesh debug config updated; restart manually to apply gateway changes.",
|
|
7
|
+
};
|
|
8
|
+
export function registerLibp2pMeshDebugCli(api, deps = {}) {
|
|
9
|
+
api.registerCli((ctx) => {
|
|
10
|
+
const root = ctx.program
|
|
11
|
+
.command("libp2p-mesh")
|
|
12
|
+
.description("Configure libp2p-mesh plugin.");
|
|
13
|
+
registerLibp2pMeshDebugCommand(root, api, ctx, deps);
|
|
14
|
+
}, LIBP2P_MESH_CLI_REGISTRATION);
|
|
15
|
+
}
|
|
16
|
+
export function registerLibp2pMeshDebugCommand(root, api, ctx, deps = {}) {
|
|
17
|
+
root
|
|
18
|
+
.command("debug")
|
|
19
|
+
.description("Manage libp2p-mesh debug logging config.")
|
|
20
|
+
.action(async () => {
|
|
21
|
+
const prompter = (deps.createPrompter?.(ctx) ?? createReadlinePrompter());
|
|
22
|
+
const writer = deps.createWriter?.(api) ?? createOpenClawDebugConfigWriter(api);
|
|
23
|
+
try {
|
|
24
|
+
const result = await runDebugWizard({
|
|
25
|
+
prompter,
|
|
26
|
+
current: getAnnounceLogDetail(ctx.config),
|
|
27
|
+
writer,
|
|
28
|
+
});
|
|
29
|
+
prompter.print(result.message);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
prompter.close?.();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
function createOpenClawDebugConfigWriter(api) {
|
|
37
|
+
return {
|
|
38
|
+
async saveAnnounceLogDetail(detail) {
|
|
39
|
+
await api.runtime.config.mutateConfigFile({
|
|
40
|
+
afterWrite: DEBUG_CLI_AFTER_WRITE,
|
|
41
|
+
mutate(draft) {
|
|
42
|
+
const nextConfig = applyAnnounceLogDetail(draft, detail);
|
|
43
|
+
replaceConfig(draft, nextConfig);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function replaceConfig(draft, nextConfig) {
|
|
50
|
+
for (const key of Object.keys(draft)) {
|
|
51
|
+
delete draft[key];
|
|
52
|
+
}
|
|
53
|
+
Object.assign(draft, structuredClone(nextConfig));
|
|
54
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type SetupPrompter } from "./setup-wizard.js";
|
|
2
|
+
import type { AnnounceLogDetail } from "./types.js";
|
|
3
|
+
export type DebugPromptChoice = AnnounceLogDetail | "cancel";
|
|
4
|
+
export type RunDebugWizardOptions = {
|
|
5
|
+
prompter: SetupPrompter;
|
|
6
|
+
current: AnnounceLogDetail;
|
|
7
|
+
writer: {
|
|
8
|
+
saveAnnounceLogDetail(detail: AnnounceLogDetail): Promise<void>;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
export type DebugWizardResult = {
|
|
12
|
+
status: "saved";
|
|
13
|
+
announceLogDetail: AnnounceLogDetail;
|
|
14
|
+
message: string;
|
|
15
|
+
} | {
|
|
16
|
+
status: "cancelled";
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function runDebugWizard(options: RunDebugWizardOptions): Promise<DebugWizardResult>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SetupCancelledError } from "./setup-wizard.js";
|
|
2
|
+
const CANCELLED_MESSAGE = "Debug configuration cancelled. No changes were written.";
|
|
3
|
+
const SAVED_MESSAGE = "Debug config updated.\n\nRestart the gateway to apply changes:\nopenclaw gateway restart";
|
|
4
|
+
export async function runDebugWizard(options) {
|
|
5
|
+
try {
|
|
6
|
+
options.prompter.print(`Current announceLogDetail: ${options.current}`);
|
|
7
|
+
const selected = await options.prompter.select("Set announceLogDetail:", [
|
|
8
|
+
{ label: "summary: log peer, instance, address and attribute counts", value: "summary" },
|
|
9
|
+
{ label: "off: disable announce summary/payload logs", value: "off" },
|
|
10
|
+
{ label: "payload: log full announce JSON", value: "payload" },
|
|
11
|
+
{ label: "Cancel", value: "cancel" },
|
|
12
|
+
]);
|
|
13
|
+
if (selected === "cancel") {
|
|
14
|
+
return cancelledResult();
|
|
15
|
+
}
|
|
16
|
+
if (selected === "payload") {
|
|
17
|
+
const confirmed = await options.prompter.confirm("Full announce payload logs may include userPublicAttributes, multiaddrs, pubkey and instance identity. Enable payload logs?", false);
|
|
18
|
+
if (!confirmed) {
|
|
19
|
+
return cancelledResult();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
await options.writer.saveAnnounceLogDetail(selected);
|
|
23
|
+
return {
|
|
24
|
+
status: "saved",
|
|
25
|
+
announceLogDetail: selected,
|
|
26
|
+
message: SAVED_MESSAGE,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
if (error instanceof SetupCancelledError) {
|
|
31
|
+
return cancelledResult();
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function cancelledResult() {
|
|
37
|
+
return {
|
|
38
|
+
status: "cancelled",
|
|
39
|
+
message: CANCELLED_MESSAGE,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -14,6 +14,17 @@ function isNonEmptyString(value) {
|
|
|
14
14
|
function summarizeError(error) {
|
|
15
15
|
return error instanceof Error ? error.message : String(error);
|
|
16
16
|
}
|
|
17
|
+
function effectiveAnnounceLogDetail(value) {
|
|
18
|
+
if (value === "off" || value === "payload") {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
return "summary";
|
|
22
|
+
}
|
|
23
|
+
function countAnnounceAttributes(payload) {
|
|
24
|
+
return Array.isArray(payload.userPublicAttributes)
|
|
25
|
+
? payload.userPublicAttributes.length
|
|
26
|
+
: 0;
|
|
27
|
+
}
|
|
17
28
|
function describeAttributeMatch(match) {
|
|
18
29
|
if (match.kind === "tag") {
|
|
19
30
|
return `tag ${match.value}`;
|
|
@@ -81,6 +92,7 @@ export function createInstanceRouter(options) {
|
|
|
81
92
|
const { mesh, store, delivery } = options;
|
|
82
93
|
const config = options.config ?? {};
|
|
83
94
|
const logger = options.logger;
|
|
95
|
+
const announceLogDetail = effectiveAnnounceLogDetail(config.announceLogDetail);
|
|
84
96
|
const ackTimeoutMs = config.deliveryAckTimeoutMs ?? 15000;
|
|
85
97
|
const announcedPeers = new Set();
|
|
86
98
|
const pendingAcks = new Map();
|
|
@@ -133,7 +145,29 @@ export function createInstanceRouter(options) {
|
|
|
133
145
|
payload: JSON.stringify(payload),
|
|
134
146
|
});
|
|
135
147
|
announcedPeers.add(peerId);
|
|
136
|
-
|
|
148
|
+
logAnnounce("Sent", peerId, payload);
|
|
149
|
+
}
|
|
150
|
+
function announceSummary(direction, peerId, payload, changed) {
|
|
151
|
+
const changedDetail = changed === undefined ? "" : ` changed=${changed}`;
|
|
152
|
+
return `[libp2p-mesh] ${direction} instance announce peer=${peerId} instance=${payload.instanceId} addrs=${payload.multiaddrs.length} attrs=${countAnnounceAttributes(payload)}${changedDetail}`;
|
|
153
|
+
}
|
|
154
|
+
function logAnnounce(direction, peerId, payload, changed) {
|
|
155
|
+
if (announceLogDetail === "off") {
|
|
156
|
+
if (direction === "Sent") {
|
|
157
|
+
logger?.info?.(`[libp2p-mesh] Sent instance announce to ${peerId} (${payload.instanceId})`);
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
logger?.info?.(announceSummary(direction, peerId, payload, changed));
|
|
162
|
+
if (announceLogDetail !== "payload") {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
logger?.debug?.(`[libp2p-mesh] ${direction} instance announce payload=${JSON.stringify(payload)}`);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
137
171
|
}
|
|
138
172
|
async function announceToConnectedPeers() {
|
|
139
173
|
for (const peerId of mesh.getConnectedPeers()) {
|
|
@@ -163,9 +197,7 @@ export function createInstanceRouter(options) {
|
|
|
163
197
|
if (result.changed) {
|
|
164
198
|
logger?.info?.(`[libp2p-mesh] Instance mapping updated: ${payload.instanceId} -> ${payload.peerId}`);
|
|
165
199
|
}
|
|
166
|
-
|
|
167
|
-
logger?.debug?.(`[libp2p-mesh] Instance mapping unchanged: ${payload.instanceId}`);
|
|
168
|
-
}
|
|
200
|
+
logAnnounce("Received", msg.from, payload, result.changed);
|
|
169
201
|
if (!announcedPeers.has(msg.from)) {
|
|
170
202
|
await announceToPeer(msg.from).catch((error) => {
|
|
171
203
|
logger?.warn?.(`[libp2p-mesh] Failed to respond to announce from ${msg.from}: ${summarizeError(error)}`);
|
|
@@ -328,7 +360,10 @@ export function createInstanceRouter(options) {
|
|
|
328
360
|
handleAck(msg);
|
|
329
361
|
}
|
|
330
362
|
}
|
|
331
|
-
|
|
363
|
+
function attachHandlers() {
|
|
364
|
+
if (unsubs.length > 0) {
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
332
367
|
unsubs.push(mesh.onMessage((msg) => {
|
|
333
368
|
handleMessage(msg).catch((error) => {
|
|
334
369
|
logger?.error?.(`[libp2p-mesh] Instance router message error: ${summarizeError(error)}`);
|
|
@@ -339,6 +374,9 @@ export function createInstanceRouter(options) {
|
|
|
339
374
|
logger?.warn?.(`[libp2p-mesh] Failed to announce to connected peer ${peerId}: ${summarizeError(error)}`);
|
|
340
375
|
});
|
|
341
376
|
}));
|
|
377
|
+
}
|
|
378
|
+
async function start() {
|
|
379
|
+
attachHandlers();
|
|
342
380
|
await announceToConnectedPeers();
|
|
343
381
|
}
|
|
344
382
|
async function stop() {
|
|
@@ -505,6 +543,8 @@ export function createInstanceRouter(options) {
|
|
|
505
543
|
};
|
|
506
544
|
}
|
|
507
545
|
return {
|
|
546
|
+
attachHandlers,
|
|
547
|
+
announceToConnectedPeers,
|
|
508
548
|
start,
|
|
509
549
|
stop,
|
|
510
550
|
handleMessage,
|
package/dist/src/plugin.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
2
|
+
import { createInstanceRouter } from "./instance-router.js";
|
|
3
|
+
import { createMeshNetwork } from "./mesh.js";
|
|
4
|
+
export type Libp2pMeshPluginDeps = {
|
|
5
|
+
createMeshNetwork?: typeof createMeshNetwork;
|
|
6
|
+
createInstanceRouter?: typeof createInstanceRouter;
|
|
7
|
+
};
|
|
2
8
|
export declare function registerLibp2pMesh(api: OpenClawPluginApi): void;
|
|
9
|
+
export declare function registerLibp2pMeshWithDeps(api: OpenClawPluginApi, deps?: Libp2pMeshPluginDeps): void;
|
package/dist/src/plugin.js
CHANGED
|
@@ -9,11 +9,15 @@ import { createUserProfileStore } from "./user-profile-store.js";
|
|
|
9
9
|
import { buildP2PTools } from "./agent-tools.js";
|
|
10
10
|
import { registerLibp2pMeshCli } from "./profile-cli.js";
|
|
11
11
|
export function registerLibp2pMesh(api) {
|
|
12
|
+
registerLibp2pMeshWithDeps(api);
|
|
13
|
+
}
|
|
14
|
+
export function registerLibp2pMeshWithDeps(api, deps = {}) {
|
|
12
15
|
registerLibp2pMeshCli(api);
|
|
13
16
|
const config = api.pluginConfig;
|
|
14
17
|
let unsubscribeInbound;
|
|
15
18
|
let serviceStarted = false;
|
|
16
|
-
|
|
19
|
+
let startPromise;
|
|
20
|
+
const mesh = (deps.createMeshNetwork ?? createMeshNetwork)({
|
|
17
21
|
config,
|
|
18
22
|
logger: api.logger,
|
|
19
23
|
});
|
|
@@ -32,7 +36,7 @@ export function registerLibp2pMesh(api) {
|
|
|
32
36
|
},
|
|
33
37
|
logger: api.logger,
|
|
34
38
|
});
|
|
35
|
-
const router = createInstanceRouter({
|
|
39
|
+
const router = (deps.createInstanceRouter ?? createInstanceRouter)({
|
|
36
40
|
mesh,
|
|
37
41
|
store,
|
|
38
42
|
delivery,
|
|
@@ -42,6 +46,62 @@ export function registerLibp2pMesh(api) {
|
|
|
42
46
|
userProfileStore,
|
|
43
47
|
});
|
|
44
48
|
const channel = createLibp2pMeshChannel(mesh);
|
|
49
|
+
function attachInboundHandlers() {
|
|
50
|
+
return mesh.onMessage((msg) => {
|
|
51
|
+
if (msg.type === "direct" || msg.type === "broadcast") {
|
|
52
|
+
const sendToChannel = async (_channelId, _target, text) => {
|
|
53
|
+
if (!config?.inboundChannel || !config?.inboundTarget) {
|
|
54
|
+
api.logger.warn?.("[libp2p-mesh] inboundChannel/inboundTarget not configured; direct message logged only.");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const result = await delivery.deliver({
|
|
58
|
+
channel: config.inboundChannel,
|
|
59
|
+
target: config.inboundTarget,
|
|
60
|
+
text,
|
|
61
|
+
metadata: {
|
|
62
|
+
fromInstanceId: msg.instanceId ?? msg.from,
|
|
63
|
+
fromPeerId: msg.from,
|
|
64
|
+
p2pMessageId: msg.id,
|
|
65
|
+
allowAgentAutoReply: false,
|
|
66
|
+
replyToInstanceId: msg.instanceId ?? msg.from,
|
|
67
|
+
replyTool: "p2p_send_instance_message",
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
if (!result.ok) {
|
|
71
|
+
api.logger.error?.(`[libp2p-mesh] Failed to forward direct message from ${msg.from}: ${result.error}`);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
handleP2PInbound(msg, { logger: api.logger, sendToChannel });
|
|
75
|
+
}
|
|
76
|
+
else if (msg.type === "agent-sync") {
|
|
77
|
+
handleP2PInbound(msg, { logger: api.logger });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
async function cleanupStartupFailure() {
|
|
82
|
+
try {
|
|
83
|
+
unsubscribeInbound?.();
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
api.logger.warn?.(`[libp2p-mesh] Failed to clean inbound handler after startup failure: ${String(error)}`);
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
unsubscribeInbound = undefined;
|
|
90
|
+
}
|
|
91
|
+
try {
|
|
92
|
+
await router.stop();
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
api.logger.warn?.(`[libp2p-mesh] Failed to stop router after startup failure: ${String(error)}`);
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await mesh.stop();
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
api.logger.warn?.(`[libp2p-mesh] Failed to stop mesh after startup failure: ${String(error)}`);
|
|
102
|
+
}
|
|
103
|
+
serviceStarted = false;
|
|
104
|
+
}
|
|
45
105
|
// 1. Register Service (manages libp2p node lifecycle)
|
|
46
106
|
api.registerService({
|
|
47
107
|
id: "libp2p-mesh",
|
|
@@ -50,56 +110,45 @@ export function registerLibp2pMesh(api) {
|
|
|
50
110
|
api.logger.debug?.("[libp2p-mesh] Service already started; ignoring duplicate start.");
|
|
51
111
|
return;
|
|
52
112
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
handleP2PInbound(msg, { logger: api.logger, sendToChannel });
|
|
113
|
+
if (startPromise) {
|
|
114
|
+
api.logger.debug?.("[libp2p-mesh] Service start already in progress; waiting for it.");
|
|
115
|
+
return startPromise;
|
|
116
|
+
}
|
|
117
|
+
startPromise = (async () => {
|
|
118
|
+
try {
|
|
119
|
+
router.attachHandlers();
|
|
120
|
+
unsubscribeInbound = attachInboundHandlers();
|
|
121
|
+
await mesh.start();
|
|
122
|
+
await router.announceToConnectedPeers();
|
|
123
|
+
const identity = mesh.getInstanceIdentity();
|
|
124
|
+
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${mesh.getLocalPeerId()}`);
|
|
125
|
+
if (identity) {
|
|
126
|
+
api.logger.info?.(`[libp2p-mesh] Instance Identity: ${identity.id}`);
|
|
127
|
+
}
|
|
128
|
+
const nat = mesh.getNATStatus();
|
|
129
|
+
const enabledNames = Object.entries(nat.enabled)
|
|
130
|
+
.filter(([, on]) => on)
|
|
131
|
+
.map(([k]) => k);
|
|
132
|
+
if (enabledNames.length > 0) {
|
|
133
|
+
api.logger.info?.(`[libp2p-mesh] NAT traversal services: ${enabledNames.join(", ")}`);
|
|
134
|
+
}
|
|
135
|
+
if (nat.reservedRelays.length > 0) {
|
|
136
|
+
api.logger.info?.(`[libp2p-mesh] Active relay reservations: ${nat.reservedRelays.join(", ")}`);
|
|
137
|
+
}
|
|
138
|
+
serviceStarted = true;
|
|
80
139
|
}
|
|
81
|
-
|
|
82
|
-
|
|
140
|
+
catch (error) {
|
|
141
|
+
await cleanupStartupFailure();
|
|
142
|
+
throw error;
|
|
83
143
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
const nat = mesh.getNATStatus();
|
|
91
|
-
const enabledNames = Object.entries(nat.enabled)
|
|
92
|
-
.filter(([, on]) => on)
|
|
93
|
-
.map(([k]) => k);
|
|
94
|
-
if (enabledNames.length > 0) {
|
|
95
|
-
api.logger.info?.(`[libp2p-mesh] NAT traversal services: ${enabledNames.join(", ")}`);
|
|
96
|
-
}
|
|
97
|
-
if (nat.reservedRelays.length > 0) {
|
|
98
|
-
api.logger.info?.(`[libp2p-mesh] Active relay reservations: ${nat.reservedRelays.join(", ")}`);
|
|
99
|
-
}
|
|
100
|
-
serviceStarted = true;
|
|
144
|
+
finally {
|
|
145
|
+
startPromise = undefined;
|
|
146
|
+
}
|
|
147
|
+
})();
|
|
148
|
+
return startPromise;
|
|
101
149
|
},
|
|
102
150
|
stop: async () => {
|
|
151
|
+
await startPromise?.catch(() => undefined);
|
|
103
152
|
unsubscribeInbound?.();
|
|
104
153
|
unsubscribeInbound = undefined;
|
|
105
154
|
await router.stop();
|
|
@@ -1,6 +1,7 @@
|
|
|
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 SetupCliDeps } from "./setup-cli.js";
|
|
4
|
+
import { type DebugCliDeps } from "./debug-cli.js";
|
|
4
5
|
import { type UserProfileStore } from "./user-profile-store.js";
|
|
5
6
|
import type { SetupPrompter } from "./setup-wizard.js";
|
|
6
7
|
type CliRootCommand = {
|
|
@@ -20,6 +21,7 @@ export type ProfileCliDeps = {
|
|
|
20
21
|
export type Libp2pMeshCliDeps = {
|
|
21
22
|
setup?: SetupCliDeps;
|
|
22
23
|
profile?: ProfileCliDeps;
|
|
24
|
+
debug?: DebugCliDeps;
|
|
23
25
|
};
|
|
24
26
|
export declare function registerLibp2pMeshCli(api: OpenClawPluginApi, deps?: Libp2pMeshCliDeps): void;
|
|
25
27
|
export declare function registerLibp2pMeshProfileCli(api: OpenClawPluginApi, deps?: ProfileCliDeps): void;
|
package/dist/src/profile-cli.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createReadlinePrompter, LIBP2P_MESH_CLI_REGISTRATION, registerLibp2pMeshSetupCommand, } from "./setup-cli.js";
|
|
2
|
+
import { registerLibp2pMeshDebugCommand } from "./debug-cli.js";
|
|
2
3
|
import { runProfileWizard } from "./profile-wizard.js";
|
|
3
4
|
import { createUserMdAttributeSource } from "./user-md-attributes.js";
|
|
4
5
|
import { createUserProfileStore } from "./user-profile-store.js";
|
|
@@ -9,6 +10,7 @@ export function registerLibp2pMeshCli(api, deps = {}) {
|
|
|
9
10
|
.description("Configure libp2p-mesh plugin.");
|
|
10
11
|
registerLibp2pMeshSetupCommand(root, api, ctx, deps.setup);
|
|
11
12
|
registerLibp2pMeshProfileCommand(root, api, ctx, deps.profile);
|
|
13
|
+
registerLibp2pMeshDebugCommand(root, api, ctx, deps.debug);
|
|
12
14
|
}, LIBP2P_MESH_CLI_REGISTRATION);
|
|
13
15
|
}
|
|
14
16
|
export function registerLibp2pMeshProfileCli(api, deps = {}) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InboundTargetConfig, MeshConfig } from "./types.js";
|
|
1
|
+
import type { AnnounceLogDetail, InboundTargetConfig, MeshConfig } from "./types.js";
|
|
2
2
|
export declare const LIBP2P_MESH_PLUGIN_ID = "libp2p-mesh";
|
|
3
3
|
export declare const DEFAULT_DELIVERY_ACK_TIMEOUT_MS = 15000;
|
|
4
4
|
export type SetupMode = "lan" | "cross-network" | "relay-node" | "tools-only";
|
|
@@ -32,11 +32,14 @@ export type AddInboundTargetResult = {
|
|
|
32
32
|
};
|
|
33
33
|
export type LegacyInboundMigrationMode = "convert" | "keep" | "replace";
|
|
34
34
|
export declare function getLibp2pMeshConfig(config: OpenClawConfigLike): MeshConfig | undefined;
|
|
35
|
+
export declare function getAnnounceLogDetail(config: OpenClawConfigLike): AnnounceLogDetail;
|
|
36
|
+
export declare function normalizeAnnounceLogDetail(value: unknown): AnnounceLogDetail;
|
|
35
37
|
export declare function buildNetworkConfig(mode: SetupMode, options?: {
|
|
36
38
|
crossNetwork?: CrossNetworkOptions;
|
|
37
39
|
relayNode?: RelayNodeOptions;
|
|
38
40
|
}): MeshConfig;
|
|
39
41
|
export declare function applyPluginConfig(config: OpenClawConfigLike, pluginConfig: MeshConfig): OpenClawConfigLike;
|
|
42
|
+
export declare function applyAnnounceLogDetail(config: OpenClawConfigLike, announceLogDetail: AnnounceLogDetail): OpenClawConfigLike;
|
|
40
43
|
export declare function mergeNetworkConfig(existing: MeshConfig | undefined, networkConfig: MeshConfig): MeshConfig;
|
|
41
44
|
export declare function listConfiguredChannels(config: OpenClawConfigLike): string[];
|
|
42
45
|
export declare function generateInboundTargetId(channel: string, existingTargets: InboundTargetConfig[]): string;
|
package/dist/src/setup-config.js
CHANGED
|
@@ -3,6 +3,12 @@ export const DEFAULT_DELIVERY_ACK_TIMEOUT_MS = 15000;
|
|
|
3
3
|
export function getLibp2pMeshConfig(config) {
|
|
4
4
|
return config.plugins?.entries?.[LIBP2P_MESH_PLUGIN_ID]?.config;
|
|
5
5
|
}
|
|
6
|
+
export function getAnnounceLogDetail(config) {
|
|
7
|
+
return normalizeAnnounceLogDetail(getLibp2pMeshConfig(config)?.announceLogDetail);
|
|
8
|
+
}
|
|
9
|
+
export function normalizeAnnounceLogDetail(value) {
|
|
10
|
+
return value === "off" || value === "payload" || value === "summary" ? value : "summary";
|
|
11
|
+
}
|
|
6
12
|
export function buildNetworkConfig(mode, options) {
|
|
7
13
|
switch (mode) {
|
|
8
14
|
case "lan":
|
|
@@ -53,6 +59,27 @@ export function applyPluginConfig(config, pluginConfig) {
|
|
|
53
59
|
},
|
|
54
60
|
};
|
|
55
61
|
}
|
|
62
|
+
export function applyAnnounceLogDetail(config, announceLogDetail) {
|
|
63
|
+
const existingEntry = config.plugins?.entries?.[LIBP2P_MESH_PLUGIN_ID];
|
|
64
|
+
const existingPluginConfig = existingEntry?.config ?? {};
|
|
65
|
+
return {
|
|
66
|
+
...config,
|
|
67
|
+
plugins: {
|
|
68
|
+
...config.plugins,
|
|
69
|
+
entries: {
|
|
70
|
+
...config.plugins?.entries,
|
|
71
|
+
[LIBP2P_MESH_PLUGIN_ID]: {
|
|
72
|
+
...existingEntry,
|
|
73
|
+
enabled: existingEntry?.enabled ?? true,
|
|
74
|
+
config: {
|
|
75
|
+
...existingPluginConfig,
|
|
76
|
+
announceLogDetail,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
56
83
|
export function mergeNetworkConfig(existing, networkConfig) {
|
|
57
84
|
if (!existing) {
|
|
58
85
|
return { ...networkConfig };
|
package/dist/src/types.d.ts
CHANGED
|
@@ -180,6 +180,8 @@ export type InstanceRouterOptions = {
|
|
|
180
180
|
};
|
|
181
181
|
};
|
|
182
182
|
export interface InstanceRouter {
|
|
183
|
+
attachHandlers(): void;
|
|
184
|
+
announceToConnectedPeers(): Promise<void>;
|
|
183
185
|
start(): Promise<void>;
|
|
184
186
|
stop(): Promise<void>;
|
|
185
187
|
handleMessage(msg: P2PMessage): Promise<void>;
|
|
@@ -206,6 +208,7 @@ export interface MeshConfig {
|
|
|
206
208
|
discovery?: "mdns" | "bootstrap" | "dht";
|
|
207
209
|
bootstrapList?: string[];
|
|
208
210
|
meshTopic?: string;
|
|
211
|
+
announceLogDetail?: AnnounceLogDetail;
|
|
209
212
|
enableAgentSync?: boolean;
|
|
210
213
|
enableWebSocket?: boolean;
|
|
211
214
|
peerIdPath?: string;
|
|
@@ -299,6 +302,7 @@ export interface MeshNetwork {
|
|
|
299
302
|
/** Inspect which NAT-traversal services are running and whether any relay reservations are active */
|
|
300
303
|
getNATStatus(): NATTraversalStatus;
|
|
301
304
|
}
|
|
305
|
+
export type AnnounceLogDetail = "off" | "summary" | "payload";
|
|
302
306
|
export type MeshAccount = {
|
|
303
307
|
accountId: string;
|
|
304
308
|
configured: boolean;
|
package/openclaw.plugin.json
CHANGED
|
@@ -35,6 +35,12 @@
|
|
|
35
35
|
"type": "string",
|
|
36
36
|
"default": "openclaw-mesh"
|
|
37
37
|
},
|
|
38
|
+
"announceLogDetail": {
|
|
39
|
+
"type": "string",
|
|
40
|
+
"enum": ["off", "summary", "payload"],
|
|
41
|
+
"default": "summary",
|
|
42
|
+
"description": "Controls instance announce logging detail."
|
|
43
|
+
},
|
|
38
44
|
"inboundTargets": {
|
|
39
45
|
"type": "array",
|
|
40
46
|
"items": {
|
|
@@ -82,6 +88,12 @@
|
|
|
82
88
|
"type": "string",
|
|
83
89
|
"default": "openclaw-mesh"
|
|
84
90
|
},
|
|
91
|
+
"announceLogDetail": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"enum": ["off", "summary", "payload"],
|
|
94
|
+
"default": "summary",
|
|
95
|
+
"description": "Controls instance announce logging detail."
|
|
96
|
+
},
|
|
85
97
|
"enablePubsub": {
|
|
86
98
|
"type": "boolean",
|
|
87
99
|
"default": true
|
package/package.json
CHANGED
package/src/debug-cli.ts
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/core";
|
|
2
|
+
import type { OpenClawPluginCliContext } from "openclaw/plugin-sdk/plugin-runtime";
|
|
3
|
+
import {
|
|
4
|
+
createReadlinePrompter,
|
|
5
|
+
LIBP2P_MESH_CLI_REGISTRATION,
|
|
6
|
+
type ClosableSetupPrompter,
|
|
7
|
+
} from "./setup-cli.js";
|
|
8
|
+
import {
|
|
9
|
+
applyAnnounceLogDetail,
|
|
10
|
+
getAnnounceLogDetail,
|
|
11
|
+
type OpenClawConfigLike,
|
|
12
|
+
} from "./setup-config.js";
|
|
13
|
+
import type { SetupPrompter } from "./setup-wizard.js";
|
|
14
|
+
import { runDebugWizard } from "./debug-wizard.js";
|
|
15
|
+
import type { AnnounceLogDetail } from "./types.js";
|
|
16
|
+
|
|
17
|
+
const DEBUG_CLI_AFTER_WRITE = {
|
|
18
|
+
mode: "none",
|
|
19
|
+
reason: "libp2p-mesh debug config updated; restart manually to apply gateway changes.",
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
type CliRootCommand = {
|
|
23
|
+
command(name: string): {
|
|
24
|
+
description(text: string): {
|
|
25
|
+
action(handler: () => Promise<void>): void;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type DebugConfigWriter = {
|
|
31
|
+
saveAnnounceLogDetail(detail: AnnounceLogDetail): Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type DebugCliDeps = {
|
|
35
|
+
createPrompter?: (ctx: OpenClawPluginCliContext) => SetupPrompter;
|
|
36
|
+
createWriter?: (api: OpenClawPluginApi) => DebugConfigWriter;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function registerLibp2pMeshDebugCli(api: OpenClawPluginApi, deps: DebugCliDeps = {}): void {
|
|
40
|
+
api.registerCli((ctx) => {
|
|
41
|
+
const root = ctx.program
|
|
42
|
+
.command("libp2p-mesh")
|
|
43
|
+
.description("Configure libp2p-mesh plugin.");
|
|
44
|
+
|
|
45
|
+
registerLibp2pMeshDebugCommand(root, api, ctx, deps);
|
|
46
|
+
}, LIBP2P_MESH_CLI_REGISTRATION);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function registerLibp2pMeshDebugCommand(
|
|
50
|
+
root: CliRootCommand,
|
|
51
|
+
api: OpenClawPluginApi,
|
|
52
|
+
ctx: OpenClawPluginCliContext,
|
|
53
|
+
deps: DebugCliDeps = {},
|
|
54
|
+
): void {
|
|
55
|
+
root
|
|
56
|
+
.command("debug")
|
|
57
|
+
.description("Manage libp2p-mesh debug logging config.")
|
|
58
|
+
.action(async () => {
|
|
59
|
+
const prompter = (deps.createPrompter?.(ctx) ?? createReadlinePrompter()) as ClosableSetupPrompter;
|
|
60
|
+
const writer = deps.createWriter?.(api) ?? createOpenClawDebugConfigWriter(api);
|
|
61
|
+
try {
|
|
62
|
+
const result = await runDebugWizard({
|
|
63
|
+
prompter,
|
|
64
|
+
current: getAnnounceLogDetail(ctx.config as OpenClawConfigLike),
|
|
65
|
+
writer,
|
|
66
|
+
});
|
|
67
|
+
prompter.print(result.message);
|
|
68
|
+
} finally {
|
|
69
|
+
prompter.close?.();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function createOpenClawDebugConfigWriter(api: OpenClawPluginApi): DebugConfigWriter {
|
|
75
|
+
return {
|
|
76
|
+
async saveAnnounceLogDetail(detail) {
|
|
77
|
+
await api.runtime.config.mutateConfigFile({
|
|
78
|
+
afterWrite: DEBUG_CLI_AFTER_WRITE,
|
|
79
|
+
mutate(draft) {
|
|
80
|
+
const nextConfig = applyAnnounceLogDetail(draft as OpenClawConfigLike, detail);
|
|
81
|
+
replaceConfig(draft as OpenClawConfig, nextConfig as OpenClawConfig);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function replaceConfig(draft: OpenClawConfig, nextConfig: OpenClawConfig): void {
|
|
89
|
+
for (const key of Object.keys(draft) as Array<keyof OpenClawConfig>) {
|
|
90
|
+
delete draft[key];
|
|
91
|
+
}
|
|
92
|
+
Object.assign(draft, structuredClone(nextConfig));
|
|
93
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { SetupCancelledError, type SetupPrompter } from "./setup-wizard.js";
|
|
2
|
+
import type { AnnounceLogDetail } from "./types.js";
|
|
3
|
+
|
|
4
|
+
const CANCELLED_MESSAGE = "Debug configuration cancelled. No changes were written.";
|
|
5
|
+
const SAVED_MESSAGE = "Debug config updated.\n\nRestart the gateway to apply changes:\nopenclaw gateway restart";
|
|
6
|
+
|
|
7
|
+
export type DebugPromptChoice = AnnounceLogDetail | "cancel";
|
|
8
|
+
|
|
9
|
+
export type RunDebugWizardOptions = {
|
|
10
|
+
prompter: SetupPrompter;
|
|
11
|
+
current: AnnounceLogDetail;
|
|
12
|
+
writer: {
|
|
13
|
+
saveAnnounceLogDetail(detail: AnnounceLogDetail): Promise<void>;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type DebugWizardResult =
|
|
18
|
+
| { status: "saved"; announceLogDetail: AnnounceLogDetail; message: string }
|
|
19
|
+
| { status: "cancelled"; message: string };
|
|
20
|
+
|
|
21
|
+
export async function runDebugWizard(options: RunDebugWizardOptions): Promise<DebugWizardResult> {
|
|
22
|
+
try {
|
|
23
|
+
options.prompter.print(`Current announceLogDetail: ${options.current}`);
|
|
24
|
+
const selected = await options.prompter.select<DebugPromptChoice>("Set announceLogDetail:", [
|
|
25
|
+
{ label: "summary: log peer, instance, address and attribute counts", value: "summary" },
|
|
26
|
+
{ label: "off: disable announce summary/payload logs", value: "off" },
|
|
27
|
+
{ label: "payload: log full announce JSON", value: "payload" },
|
|
28
|
+
{ label: "Cancel", value: "cancel" },
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
if (selected === "cancel") {
|
|
32
|
+
return cancelledResult();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (selected === "payload") {
|
|
36
|
+
const confirmed = await options.prompter.confirm(
|
|
37
|
+
"Full announce payload logs may include userPublicAttributes, multiaddrs, pubkey and instance identity. Enable payload logs?",
|
|
38
|
+
false,
|
|
39
|
+
);
|
|
40
|
+
if (!confirmed) {
|
|
41
|
+
return cancelledResult();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await options.writer.saveAnnounceLogDetail(selected);
|
|
46
|
+
return {
|
|
47
|
+
status: "saved",
|
|
48
|
+
announceLogDetail: selected,
|
|
49
|
+
message: SAVED_MESSAGE,
|
|
50
|
+
};
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error instanceof SetupCancelledError) {
|
|
53
|
+
return cancelledResult();
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function cancelledResult(): DebugWizardResult {
|
|
60
|
+
return {
|
|
61
|
+
status: "cancelled",
|
|
62
|
+
message: CANCELLED_MESSAGE,
|
|
63
|
+
};
|
|
64
|
+
}
|
package/src/instance-router.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AnnounceLogDetail,
|
|
2
3
|
DeliveryAckPayload,
|
|
3
4
|
DeliveryTargetResult,
|
|
4
5
|
InboundTargetConfig,
|
|
@@ -50,6 +51,20 @@ function summarizeError(error: unknown): string {
|
|
|
50
51
|
return error instanceof Error ? error.message : String(error);
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
function effectiveAnnounceLogDetail(value: unknown): AnnounceLogDetail {
|
|
55
|
+
if (value === "off" || value === "payload") {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return "summary";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function countAnnounceAttributes(payload: InstanceAnnouncePayload): number {
|
|
63
|
+
return Array.isArray(payload.userPublicAttributes)
|
|
64
|
+
? payload.userPublicAttributes.length
|
|
65
|
+
: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
53
68
|
function describeAttributeMatch(match: UserAttributeMatch): string {
|
|
54
69
|
if (match.kind === "tag") {
|
|
55
70
|
return `tag ${match.value}`;
|
|
@@ -149,6 +164,7 @@ export function createInstanceRouter(options: InstanceRouterOptions): InstanceRo
|
|
|
149
164
|
const { mesh, store, delivery } = options;
|
|
150
165
|
const config = options.config ?? {};
|
|
151
166
|
const logger = options.logger;
|
|
167
|
+
const announceLogDetail = effectiveAnnounceLogDetail(config.announceLogDetail);
|
|
152
168
|
const ackTimeoutMs = config.deliveryAckTimeoutMs ?? 15000;
|
|
153
169
|
const announcedPeers = new Set<string>();
|
|
154
170
|
const pendingAcks = new Map<string, PendingAck>();
|
|
@@ -212,9 +228,46 @@ export function createInstanceRouter(options: InstanceRouterOptions): InstanceRo
|
|
|
212
228
|
payload: JSON.stringify(payload),
|
|
213
229
|
});
|
|
214
230
|
announcedPeers.add(peerId);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
231
|
+
logAnnounce("Sent", peerId, payload);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function announceSummary(
|
|
235
|
+
direction: "Sent" | "Received",
|
|
236
|
+
peerId: string,
|
|
237
|
+
payload: InstanceAnnouncePayload,
|
|
238
|
+
changed?: boolean,
|
|
239
|
+
): string {
|
|
240
|
+
const changedDetail = changed === undefined ? "" : ` changed=${changed}`;
|
|
241
|
+
return `[libp2p-mesh] ${direction} instance announce peer=${peerId} instance=${payload.instanceId} addrs=${payload.multiaddrs.length} attrs=${countAnnounceAttributes(payload)}${changedDetail}`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function logAnnounce(
|
|
245
|
+
direction: "Sent" | "Received",
|
|
246
|
+
peerId: string,
|
|
247
|
+
payload: InstanceAnnouncePayload,
|
|
248
|
+
changed?: boolean,
|
|
249
|
+
): void {
|
|
250
|
+
if (announceLogDetail === "off") {
|
|
251
|
+
if (direction === "Sent") {
|
|
252
|
+
logger?.info?.(
|
|
253
|
+
`[libp2p-mesh] Sent instance announce to ${peerId} (${payload.instanceId})`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
logger?.info?.(announceSummary(direction, peerId, payload, changed));
|
|
260
|
+
if (announceLogDetail !== "payload") {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
logger?.debug?.(
|
|
266
|
+
`[libp2p-mesh] ${direction} instance announce payload=${JSON.stringify(payload)}`,
|
|
267
|
+
);
|
|
268
|
+
} catch {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
218
271
|
}
|
|
219
272
|
|
|
220
273
|
async function announceToConnectedPeers(): Promise<void> {
|
|
@@ -255,9 +308,8 @@ export function createInstanceRouter(options: InstanceRouterOptions): InstanceRo
|
|
|
255
308
|
logger?.info?.(
|
|
256
309
|
`[libp2p-mesh] Instance mapping updated: ${payload.instanceId} -> ${payload.peerId}`,
|
|
257
310
|
);
|
|
258
|
-
} else {
|
|
259
|
-
logger?.debug?.(`[libp2p-mesh] Instance mapping unchanged: ${payload.instanceId}`);
|
|
260
311
|
}
|
|
312
|
+
logAnnounce("Received", msg.from, payload, result.changed);
|
|
261
313
|
|
|
262
314
|
if (!announcedPeers.has(msg.from)) {
|
|
263
315
|
await announceToPeer(msg.from).catch((error) => {
|
|
@@ -449,7 +501,11 @@ export function createInstanceRouter(options: InstanceRouterOptions): InstanceRo
|
|
|
449
501
|
}
|
|
450
502
|
}
|
|
451
503
|
|
|
452
|
-
|
|
504
|
+
function attachHandlers(): void {
|
|
505
|
+
if (unsubs.length > 0) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
453
509
|
unsubs.push(
|
|
454
510
|
mesh.onMessage((msg) => {
|
|
455
511
|
handleMessage(msg).catch((error) => {
|
|
@@ -468,7 +524,10 @@ export function createInstanceRouter(options: InstanceRouterOptions): InstanceRo
|
|
|
468
524
|
});
|
|
469
525
|
}),
|
|
470
526
|
);
|
|
527
|
+
}
|
|
471
528
|
|
|
529
|
+
async function start(): Promise<void> {
|
|
530
|
+
attachHandlers();
|
|
472
531
|
await announceToConnectedPeers();
|
|
473
532
|
}
|
|
474
533
|
|
|
@@ -658,6 +717,8 @@ export function createInstanceRouter(options: InstanceRouterOptions): InstanceRo
|
|
|
658
717
|
}
|
|
659
718
|
|
|
660
719
|
return {
|
|
720
|
+
attachHandlers,
|
|
721
|
+
announceToConnectedPeers,
|
|
661
722
|
start,
|
|
662
723
|
stop,
|
|
663
724
|
handleMessage,
|
package/src/plugin.ts
CHANGED
|
@@ -12,13 +12,26 @@ import { registerLibp2pMeshCli } from "./profile-cli.js";
|
|
|
12
12
|
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
|
|
13
13
|
import type { MeshConfig } from "./types.js";
|
|
14
14
|
|
|
15
|
+
export type Libp2pMeshPluginDeps = {
|
|
16
|
+
createMeshNetwork?: typeof createMeshNetwork;
|
|
17
|
+
createInstanceRouter?: typeof createInstanceRouter;
|
|
18
|
+
};
|
|
19
|
+
|
|
15
20
|
export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
21
|
+
registerLibp2pMeshWithDeps(api);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function registerLibp2pMeshWithDeps(
|
|
25
|
+
api: OpenClawPluginApi,
|
|
26
|
+
deps: Libp2pMeshPluginDeps = {},
|
|
27
|
+
) {
|
|
16
28
|
registerLibp2pMeshCli(api);
|
|
17
29
|
|
|
18
30
|
const config = api.pluginConfig as MeshConfig | undefined;
|
|
19
31
|
let unsubscribeInbound: (() => void) | undefined;
|
|
20
32
|
let serviceStarted = false;
|
|
21
|
-
|
|
33
|
+
let startPromise: Promise<void> | undefined;
|
|
34
|
+
const mesh = (deps.createMeshNetwork ?? createMeshNetwork)({
|
|
22
35
|
config,
|
|
23
36
|
logger: api.logger,
|
|
24
37
|
});
|
|
@@ -39,7 +52,7 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
39
52
|
},
|
|
40
53
|
logger: api.logger,
|
|
41
54
|
});
|
|
42
|
-
const router = createInstanceRouter({
|
|
55
|
+
const router = (deps.createInstanceRouter ?? createInstanceRouter)({
|
|
43
56
|
mesh,
|
|
44
57
|
store,
|
|
45
58
|
delivery,
|
|
@@ -51,6 +64,73 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
51
64
|
|
|
52
65
|
const channel = createLibp2pMeshChannel(mesh);
|
|
53
66
|
|
|
67
|
+
function attachInboundHandlers(): () => void {
|
|
68
|
+
return mesh.onMessage((msg) => {
|
|
69
|
+
if (msg.type === "direct" || msg.type === "broadcast") {
|
|
70
|
+
const sendToChannel: InboundHandlerDeps["sendToChannel"] = async (_channelId, _target, text) => {
|
|
71
|
+
if (!config?.inboundChannel || !config?.inboundTarget) {
|
|
72
|
+
api.logger.warn?.(
|
|
73
|
+
"[libp2p-mesh] inboundChannel/inboundTarget not configured; direct message logged only.",
|
|
74
|
+
);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const result = await delivery.deliver({
|
|
79
|
+
channel: config.inboundChannel,
|
|
80
|
+
target: config.inboundTarget,
|
|
81
|
+
text,
|
|
82
|
+
metadata: {
|
|
83
|
+
fromInstanceId: msg.instanceId ?? msg.from,
|
|
84
|
+
fromPeerId: msg.from,
|
|
85
|
+
p2pMessageId: msg.id,
|
|
86
|
+
allowAgentAutoReply: false,
|
|
87
|
+
replyToInstanceId: msg.instanceId ?? msg.from,
|
|
88
|
+
replyTool: "p2p_send_instance_message",
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
if (!result.ok) {
|
|
92
|
+
api.logger.error?.(
|
|
93
|
+
`[libp2p-mesh] Failed to forward direct message from ${msg.from}: ${result.error}`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
handleP2PInbound(msg, { logger: api.logger, sendToChannel });
|
|
98
|
+
} else if (msg.type === "agent-sync") {
|
|
99
|
+
handleP2PInbound(msg, { logger: api.logger });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function cleanupStartupFailure(): Promise<void> {
|
|
105
|
+
try {
|
|
106
|
+
unsubscribeInbound?.();
|
|
107
|
+
} catch (error) {
|
|
108
|
+
api.logger.warn?.(
|
|
109
|
+
`[libp2p-mesh] Failed to clean inbound handler after startup failure: ${String(error)}`,
|
|
110
|
+
);
|
|
111
|
+
} finally {
|
|
112
|
+
unsubscribeInbound = undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await router.stop();
|
|
117
|
+
} catch (error) {
|
|
118
|
+
api.logger.warn?.(
|
|
119
|
+
`[libp2p-mesh] Failed to stop router after startup failure: ${String(error)}`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
await mesh.stop();
|
|
125
|
+
} catch (error) {
|
|
126
|
+
api.logger.warn?.(
|
|
127
|
+
`[libp2p-mesh] Failed to stop mesh after startup failure: ${String(error)}`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
serviceStarted = false;
|
|
132
|
+
}
|
|
133
|
+
|
|
54
134
|
// 1. Register Service (manages libp2p node lifecycle)
|
|
55
135
|
api.registerService({
|
|
56
136
|
id: "libp2p-mesh",
|
|
@@ -59,64 +139,52 @@ export function registerLibp2pMesh(api: OpenClawPluginApi) {
|
|
|
59
139
|
api.logger.debug?.("[libp2p-mesh] Service already started; ignoring duplicate start.");
|
|
60
140
|
return;
|
|
61
141
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const sendToChannel: InboundHandlerDeps["sendToChannel"] = async (_channelId, _target, text) => {
|
|
67
|
-
if (!config?.inboundChannel || !config?.inboundTarget) {
|
|
68
|
-
api.logger.warn?.(
|
|
69
|
-
"[libp2p-mesh] inboundChannel/inboundTarget not configured; direct message logged only.",
|
|
70
|
-
);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const result = await delivery.deliver({
|
|
75
|
-
channel: config.inboundChannel,
|
|
76
|
-
target: config.inboundTarget,
|
|
77
|
-
text,
|
|
78
|
-
metadata: {
|
|
79
|
-
fromInstanceId: msg.instanceId ?? msg.from,
|
|
80
|
-
fromPeerId: msg.from,
|
|
81
|
-
p2pMessageId: msg.id,
|
|
82
|
-
allowAgentAutoReply: false,
|
|
83
|
-
replyToInstanceId: msg.instanceId ?? msg.from,
|
|
84
|
-
replyTool: "p2p_send_instance_message",
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
if (!result.ok) {
|
|
88
|
-
api.logger.error?.(
|
|
89
|
-
`[libp2p-mesh] Failed to forward direct message from ${msg.from}: ${result.error}`,
|
|
90
|
-
);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
handleP2PInbound(msg, { logger: api.logger, sendToChannel });
|
|
94
|
-
} else if (msg.type === "agent-sync") {
|
|
95
|
-
handleP2PInbound(msg, { logger: api.logger });
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
const identity = mesh.getInstanceIdentity();
|
|
99
|
-
api.logger.info?.(`[libp2p-mesh] Service started. Peer ID: ${mesh.getLocalPeerId()}`);
|
|
100
|
-
if (identity) {
|
|
101
|
-
api.logger.info?.(`[libp2p-mesh] Instance Identity: ${identity.id}`);
|
|
102
|
-
}
|
|
103
|
-
const nat = mesh.getNATStatus();
|
|
104
|
-
const enabledNames = Object.entries(nat.enabled)
|
|
105
|
-
.filter(([, on]) => on)
|
|
106
|
-
.map(([k]) => k);
|
|
107
|
-
if (enabledNames.length > 0) {
|
|
108
|
-
api.logger.info?.(
|
|
109
|
-
`[libp2p-mesh] NAT traversal services: ${enabledNames.join(", ")}`,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
if (nat.reservedRelays.length > 0) {
|
|
113
|
-
api.logger.info?.(
|
|
114
|
-
`[libp2p-mesh] Active relay reservations: ${nat.reservedRelays.join(", ")}`,
|
|
115
|
-
);
|
|
142
|
+
|
|
143
|
+
if (startPromise) {
|
|
144
|
+
api.logger.debug?.("[libp2p-mesh] Service start already in progress; waiting for it.");
|
|
145
|
+
return startPromise;
|
|
116
146
|
}
|
|
117
|
-
|
|
147
|
+
|
|
148
|
+
startPromise = (async () => {
|
|
149
|
+
try {
|
|
150
|
+
router.attachHandlers();
|
|
151
|
+
unsubscribeInbound = attachInboundHandlers();
|
|
152
|
+
await mesh.start();
|
|
153
|
+
await router.announceToConnectedPeers();
|
|
154
|
+
const identity = mesh.getInstanceIdentity();
|
|
155
|
+
api.logger.info?.(
|
|
156
|
+
`[libp2p-mesh] Service started. Peer ID: ${mesh.getLocalPeerId()}`,
|
|
157
|
+
);
|
|
158
|
+
if (identity) {
|
|
159
|
+
api.logger.info?.(`[libp2p-mesh] Instance Identity: ${identity.id}`);
|
|
160
|
+
}
|
|
161
|
+
const nat = mesh.getNATStatus();
|
|
162
|
+
const enabledNames = Object.entries(nat.enabled)
|
|
163
|
+
.filter(([, on]) => on)
|
|
164
|
+
.map(([k]) => k);
|
|
165
|
+
if (enabledNames.length > 0) {
|
|
166
|
+
api.logger.info?.(
|
|
167
|
+
`[libp2p-mesh] NAT traversal services: ${enabledNames.join(", ")}`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (nat.reservedRelays.length > 0) {
|
|
171
|
+
api.logger.info?.(
|
|
172
|
+
`[libp2p-mesh] Active relay reservations: ${nat.reservedRelays.join(", ")}`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
serviceStarted = true;
|
|
176
|
+
} catch (error) {
|
|
177
|
+
await cleanupStartupFailure();
|
|
178
|
+
throw error;
|
|
179
|
+
} finally {
|
|
180
|
+
startPromise = undefined;
|
|
181
|
+
}
|
|
182
|
+
})();
|
|
183
|
+
|
|
184
|
+
return startPromise;
|
|
118
185
|
},
|
|
119
186
|
stop: async () => {
|
|
187
|
+
await startPromise?.catch(() => undefined);
|
|
120
188
|
unsubscribeInbound?.();
|
|
121
189
|
unsubscribeInbound = undefined;
|
|
122
190
|
await router.stop();
|
package/src/profile-cli.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type ClosableSetupPrompter,
|
|
8
8
|
type SetupCliDeps,
|
|
9
9
|
} from "./setup-cli.js";
|
|
10
|
+
import { registerLibp2pMeshDebugCommand, type DebugCliDeps } from "./debug-cli.js";
|
|
10
11
|
import { runProfileWizard } from "./profile-wizard.js";
|
|
11
12
|
import { createUserMdAttributeSource } from "./user-md-attributes.js";
|
|
12
13
|
import { createUserProfileStore, type UserProfileStore } from "./user-profile-store.js";
|
|
@@ -29,6 +30,7 @@ export type ProfileCliDeps = {
|
|
|
29
30
|
export type Libp2pMeshCliDeps = {
|
|
30
31
|
setup?: SetupCliDeps;
|
|
31
32
|
profile?: ProfileCliDeps;
|
|
33
|
+
debug?: DebugCliDeps;
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
export function registerLibp2pMeshCli(api: OpenClawPluginApi, deps: Libp2pMeshCliDeps = {}): void {
|
|
@@ -39,6 +41,7 @@ export function registerLibp2pMeshCli(api: OpenClawPluginApi, deps: Libp2pMeshCl
|
|
|
39
41
|
|
|
40
42
|
registerLibp2pMeshSetupCommand(root, api, ctx, deps.setup);
|
|
41
43
|
registerLibp2pMeshProfileCommand(root, api, ctx, deps.profile);
|
|
44
|
+
registerLibp2pMeshDebugCommand(root, api, ctx, deps.debug);
|
|
42
45
|
}, LIBP2P_MESH_CLI_REGISTRATION);
|
|
43
46
|
}
|
|
44
47
|
|
package/src/setup-config.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { InboundTargetConfig, MeshConfig } from "./types.js";
|
|
1
|
+
import type { AnnounceLogDetail, InboundTargetConfig, MeshConfig } from "./types.js";
|
|
2
2
|
|
|
3
3
|
export const LIBP2P_MESH_PLUGIN_ID = "libp2p-mesh";
|
|
4
4
|
export const DEFAULT_DELIVERY_ACK_TIMEOUT_MS = 15000;
|
|
@@ -38,6 +38,14 @@ export function getLibp2pMeshConfig(config: OpenClawConfigLike): MeshConfig | un
|
|
|
38
38
|
return config.plugins?.entries?.[LIBP2P_MESH_PLUGIN_ID]?.config as MeshConfig | undefined;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
export function getAnnounceLogDetail(config: OpenClawConfigLike): AnnounceLogDetail {
|
|
42
|
+
return normalizeAnnounceLogDetail(getLibp2pMeshConfig(config)?.announceLogDetail);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function normalizeAnnounceLogDetail(value: unknown): AnnounceLogDetail {
|
|
46
|
+
return value === "off" || value === "payload" || value === "summary" ? value : "summary";
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
export function buildNetworkConfig(
|
|
42
50
|
mode: SetupMode,
|
|
43
51
|
options?: {
|
|
@@ -99,6 +107,32 @@ export function applyPluginConfig(config: OpenClawConfigLike, pluginConfig: Mesh
|
|
|
99
107
|
};
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
export function applyAnnounceLogDetail(
|
|
111
|
+
config: OpenClawConfigLike,
|
|
112
|
+
announceLogDetail: AnnounceLogDetail,
|
|
113
|
+
): OpenClawConfigLike {
|
|
114
|
+
const existingEntry = config.plugins?.entries?.[LIBP2P_MESH_PLUGIN_ID];
|
|
115
|
+
const existingPluginConfig = existingEntry?.config ?? {};
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
...config,
|
|
119
|
+
plugins: {
|
|
120
|
+
...config.plugins,
|
|
121
|
+
entries: {
|
|
122
|
+
...config.plugins?.entries,
|
|
123
|
+
[LIBP2P_MESH_PLUGIN_ID]: {
|
|
124
|
+
...existingEntry,
|
|
125
|
+
enabled: existingEntry?.enabled ?? true,
|
|
126
|
+
config: {
|
|
127
|
+
...existingPluginConfig,
|
|
128
|
+
announceLogDetail,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
102
136
|
export function mergeNetworkConfig(existing: MeshConfig | undefined, networkConfig: MeshConfig): MeshConfig {
|
|
103
137
|
if (!existing) {
|
|
104
138
|
return { ...networkConfig };
|
package/src/types.ts
CHANGED
|
@@ -203,6 +203,8 @@ export type InstanceRouterOptions = {
|
|
|
203
203
|
};
|
|
204
204
|
|
|
205
205
|
export interface InstanceRouter {
|
|
206
|
+
attachHandlers(): void;
|
|
207
|
+
announceToConnectedPeers(): Promise<void>;
|
|
206
208
|
start(): Promise<void>;
|
|
207
209
|
stop(): Promise<void>;
|
|
208
210
|
handleMessage(msg: P2PMessage): Promise<void>;
|
|
@@ -232,6 +234,7 @@ export interface MeshConfig {
|
|
|
232
234
|
discovery?: "mdns" | "bootstrap" | "dht";
|
|
233
235
|
bootstrapList?: string[];
|
|
234
236
|
meshTopic?: string;
|
|
237
|
+
announceLogDetail?: AnnounceLogDetail;
|
|
235
238
|
enableAgentSync?: boolean;
|
|
236
239
|
enableWebSocket?: boolean;
|
|
237
240
|
peerIdPath?: string;
|
|
@@ -335,6 +338,8 @@ export interface MeshNetwork {
|
|
|
335
338
|
getNATStatus(): NATTraversalStatus;
|
|
336
339
|
}
|
|
337
340
|
|
|
341
|
+
export type AnnounceLogDetail = "off" | "summary" | "payload";
|
|
342
|
+
|
|
338
343
|
export type MeshAccount = {
|
|
339
344
|
accountId: string;
|
|
340
345
|
configured: boolean;
|