rol-websocket-channel 1.0.2 → 1.0.6

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/index.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  import type { ReplyPayload } from "openclaw/auto-reply/types";
11
11
  import type { ChannelPlugin, OpenClawConfig } from "openclaw/plugin-sdk";
12
12
  import { messageHandler } from "./message-handler.js";
13
- import { MqttClient } from "./src/mqtt/mqtt-client.js";
13
+ import { GlobalMqttClient } from "./src/mqtt/mqtt-client.js";
14
14
  import * as ConnectionManager from "./src/mqtt/connection-manager.js";
15
15
 
16
16
  // ============================================
@@ -78,7 +78,7 @@ const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
78
78
  },
79
79
  allowFrom: {
80
80
  type: "array",
81
- items: { type: "string" }
81
+ items: { type: "string" },
82
82
  },
83
83
  pairing: {
84
84
  type: "object",
@@ -88,16 +88,16 @@ const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
88
88
  pairedAt: { type: "string" },
89
89
  pairingKeyLast4: { type: "string" },
90
90
  userId: { type: "string" },
91
- rawValue: { type: "string" }
92
- }
91
+ rawValue: { type: "string" },
92
+ },
93
93
  },
94
94
  apiCoreBot: {
95
95
  type: "object",
96
96
  additionalProperties: false,
97
97
  properties: {
98
98
  baseUrl: { type: "string" },
99
- authToken: { type: "string" }
100
- }
99
+ authToken: { type: "string" },
100
+ },
101
101
  },
102
102
  config: {
103
103
  type: "object",
@@ -117,15 +117,33 @@ const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
117
117
  },
118
118
  uiHints: {
119
119
  enabled: { label: "Enabled", description: "Enable MQTT Channel" },
120
- dmPolicy: { label: "DM Policy", description: "Pairing/allowlist/open policy for direct messages" },
121
- allowFrom: { label: "Allow From", description: "Allowed sender IDs when using allowlist or pairing mode" },
122
- pairing: { label: "Pairing", description: "Pairing status and resolved identity info" },
123
- apiCoreBot: { label: "API Core Bot", description: "Backend API endpoint and auth token used by the plugin" },
124
- config: { label: "Configuration", description: "MQTT connection configuration" },
125
- "config.enabled": { label: "Enabled", description: "Enable this configuration" },
120
+ dmPolicy: {
121
+ label: "DM Policy",
122
+ description: "Pairing/allowlist/open policy for direct messages",
123
+ },
124
+ allowFrom: {
125
+ label: "Allow From",
126
+ description: "Allowed sender IDs when using allowlist or pairing mode",
127
+ },
128
+ pairing: {
129
+ label: "Pairing",
130
+ description: "Pairing status and resolved identity info",
131
+ },
132
+ apiCoreBot: {
133
+ label: "API Core Bot",
134
+ description: "Backend API endpoint and auth token used by the plugin",
135
+ },
136
+ config: {
137
+ label: "Configuration",
138
+ description: "MQTT connection configuration",
139
+ },
140
+ "config.enabled": {
141
+ label: "Enabled",
142
+ description: "Enable this configuration",
143
+ },
126
144
  "config.mqttUrl": {
127
145
  label: "MQTT Broker URL",
128
- placeholder: "ws://192.168.1.23:8083/mqtt",
146
+ placeholder: "ws://192.168.1.152:8083/mqtt",
129
147
  help: "MQTT broker WebSocket URL",
130
148
  },
131
149
  "config.mqttTopic": {
@@ -133,7 +151,10 @@ const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
133
151
  placeholder: "announcement/tester",
134
152
  help: "MQTT topic to subscribe/publish",
135
153
  },
136
- "config.groupPolicy": { label: "Group Policy", description: "Message policy for group chats" },
154
+ "config.groupPolicy": {
155
+ label: "Group Policy",
156
+ description: "Message policy for group chats",
157
+ },
137
158
  },
138
159
  },
139
160
 
@@ -156,11 +177,13 @@ const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
156
177
 
157
178
  return {
158
179
  accountId: "default",
159
- mqttUrl: config.mqttUrl || "ws://192.168.1.23:8083/mqtt",
180
+ mqttUrl: config.mqttUrl || "ws://192.168.1.152:8083/mqtt",
160
181
  mqttTopic: config.mqttTopic || "announcement/tester",
161
182
  enabled: config.enabled !== false,
162
183
  dmPolicy: channelCfg.dmPolicy || config.groupPolicy || "open",
163
- allowFrom: Array.isArray(channelCfg.allowFrom) ? channelCfg.allowFrom : [],
184
+ allowFrom: Array.isArray(channelCfg.allowFrom)
185
+ ? channelCfg.allowFrom
186
+ : [],
164
187
  groupPolicy: config.groupPolicy || "open",
165
188
  };
166
189
  },
@@ -208,27 +231,36 @@ const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
208
231
  outbound: {
209
232
  deliveryMode: "direct",
210
233
 
211
- sendText: async ({ to, text, accountId }) => {
212
- const conn = ConnectionManager.getConnection(accountId ?? "default");
234
+ sendText: async ({ to, text }) => {
235
+ const conn = ConnectionManager.getGlobalConnection();
213
236
  if (!conn || !conn.ws || !conn.ws.connected) {
214
237
  return { ok: false, error: "No MQTT connection" };
215
238
  }
216
239
 
217
- const mqttTopic = conn.mqttTopic || "announcement/tester";
218
- const message = JSON.stringify({ type: "message", to, content: text, timestamp: Date.now() });
219
- conn.ws.publish(mqttTopic, message);
240
+ const message = JSON.stringify({
241
+ type: "message",
242
+ to,
243
+ content: text,
244
+ timestamp: Date.now(),
245
+ });
246
+ conn.ws.publish(conn.topic, message);
220
247
  return { ok: true };
221
248
  },
222
249
 
223
- sendMedia: async ({ to, text, mediaUrl, accountId }) => {
224
- const conn = ConnectionManager.getConnection(accountId ?? "default");
250
+ sendMedia: async ({ to, text, mediaUrl }) => {
251
+ const conn = ConnectionManager.getGlobalConnection();
225
252
  if (!conn || !conn.ws || !conn.ws.connected) {
226
253
  return { ok: false, error: "No MQTT connection" };
227
254
  }
228
255
 
229
- const mqttTopic = conn.mqttTopic || "announcement/tester";
230
- const message = JSON.stringify({ type: "media", to, content: text, mediaUrl, timestamp: Date.now() });
231
- conn.ws.publish(mqttTopic, message);
256
+ const message = JSON.stringify({
257
+ type: "media",
258
+ to,
259
+ content: text,
260
+ mediaUrl,
261
+ timestamp: Date.now(),
262
+ });
263
+ conn.ws.publish(conn.topic, message);
232
264
  return { ok: true };
233
265
  },
234
266
  },
@@ -239,22 +271,30 @@ const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
239
271
  const runtime = pluginRuntime;
240
272
 
241
273
  console.log(`[MQTT] startAccount(${account.accountId}): starting...`);
242
- log?.info(`[rol-websocket-channel] Starting MQTT Channel for ${account.accountId}`);
274
+ log?.info(
275
+ `[rol-websocket-channel] Starting MQTT Channel for ${account.accountId}`,
276
+ );
243
277
 
244
278
  // 检查是否已有活跃连接,防止重复启动
245
- if (ConnectionManager.isConnected(account.accountId)) {
246
- console.log(`[MQTT] startAccount(${account.accountId}): already connected, skipping`);
279
+ if (ConnectionManager.isGlobalConnected()) {
280
+ console.log(
281
+ `[MQTT] startAccount(${account.accountId}): already connected, skipping`,
282
+ );
247
283
  return;
248
284
  }
249
285
 
250
286
  if (!runtime?.channel?.reply?.withReplyDispatcher) {
251
- console.error(`[MQTT] startAccount(${account.accountId}): Runtime API not available`);
287
+ console.error(
288
+ `[MQTT] startAccount(${account.accountId}): Runtime API not available`,
289
+ );
252
290
  throw new Error("Runtime API not available");
253
291
  }
254
292
 
255
- const mqttUrl = account.mqttUrl || "ws://192.168.1.23:8083/mqtt";
293
+ const mqttUrl = account.mqttUrl || "ws://192.168.1.152:8083/mqtt";
256
294
  const mqttTopic = account.mqttTopic || "announcement/tester";
257
- console.log(`[MQTT] startAccount(${account.accountId}): url=${mqttUrl}, topic=${mqttTopic}`);
295
+ console.log(
296
+ `[MQTT] startAccount(${account.accountId}): url=${mqttUrl}, topic=${mqttTopic}`,
297
+ );
258
298
 
259
299
  ctx.setStatus({
260
300
  accountId: account.accountId,
@@ -266,50 +306,69 @@ const WebSocketChannel: ChannelPlugin<WebSocketChannelAccount> = {
266
306
  });
267
307
 
268
308
  // 创建 MQTT 客户端
269
- console.log(`[MQTT] startAccount(${account.accountId}): creating MqttClient...`);
270
- const client = new MqttClient({
271
- config: {
272
- accountId: account.accountId,
273
- mqttUrl,
274
- mqttTopic,
275
- groupPolicy: account.groupPolicy,
276
- },
309
+ console.log(
310
+ `[MQTT] startAccount(${account.accountId}): creating GlobalMqttClient...`,
311
+ );
312
+ const client = new GlobalMqttClient({
313
+ mqttUrl,
314
+ mqttTopic,
277
315
  abortSignal,
278
316
  onConnect: () => {
279
- console.log(`[MQTT] startAccount(${account.accountId}): onConnect - setting status to connected`);
317
+ console.log(
318
+ `[MQTT] startAccount(${account.accountId}): onConnect - setting status to connected`,
319
+ );
280
320
  ctx.setStatus({
281
321
  accountId: account.accountId,
282
322
  connected: true,
283
323
  });
284
324
  },
285
325
  onDisconnect: () => {
286
- console.log(`[MQTT] startAccount(${account.accountId}): onDisconnect - setting status to disconnected`);
326
+ console.log(
327
+ `[MQTT] startAccount(${account.accountId}): onDisconnect - setting status to disconnected`,
328
+ );
287
329
  ctx.setStatus({
288
330
  accountId: account.accountId,
289
331
  connected: false,
290
332
  });
291
333
  },
292
334
  onError: (err) => {
293
- console.error(`[MQTT] startAccount(${account.accountId}): onError - ${err.message}`);
335
+ console.error(
336
+ `[MQTT] startAccount(${account.accountId}): onError - ${err.message}`,
337
+ );
294
338
  log?.error(`[rol-websocket-channel] MQTT error: ${err.message}`);
295
339
  },
296
340
  onMessage: async (topic: string, payload: Buffer) => {
297
- console.log(`[MQTT] startAccount(${account.accountId}): onMessage received`);
298
- await handleIncomingMessage(payload, account, cfg, runtime, log, mqttTopic);
341
+ console.log(
342
+ `[MQTT] startAccount(${account.accountId}): onMessage received`,
343
+ );
344
+ await handleIncomingMessage(
345
+ payload,
346
+ account,
347
+ cfg,
348
+ runtime,
349
+ log,
350
+ mqttTopic,
351
+ );
299
352
  },
300
353
  });
301
354
 
302
355
  // 启动连接
303
- console.log(`[MQTT] startAccount(${account.accountId}): calling client.connect()...`);
356
+ console.log(
357
+ `[MQTT] startAccount(${account.accountId}): calling GlobalMqttClient.connect()...`,
358
+ );
304
359
  await client.connect();
305
- console.log(`[MQTT] startAccount(${account.accountId}): client.connect() returned`);
360
+ console.log(
361
+ `[MQTT] startAccount(${account.accountId}): GlobalMqttClient.connect() returned`,
362
+ );
306
363
  },
307
364
 
308
365
  stopAccount: async (ctx) => {
309
366
  const { log, account } = ctx;
310
367
  console.log(`[MQTT] stopAccount(${account.accountId}): stopping...`);
311
- log?.info(`[rol-websocket-channel] Stopping MQTT Channel for ${account.accountId}`);
312
- ConnectionManager.closeConnection(account.accountId);
368
+ log?.info(
369
+ `[rol-websocket-channel] Stopping MQTT Channel for ${account.accountId}`,
370
+ );
371
+ ConnectionManager.closeGlobalConnection();
313
372
  console.log(`[MQTT] stopAccount(${account.accountId}): stopped`);
314
373
  },
315
374
  },
@@ -349,7 +408,13 @@ async function handleIncomingMessage(
349
408
 
350
409
  // 处理非标准消息类型
351
410
  if (msgType !== "sender") {
352
- await handleCustomMessageType(msgType, innerData, traceId, account.accountId, mqttTopic);
411
+ await handleCustomMessageType(
412
+ msgType,
413
+ innerData,
414
+ traceId,
415
+ account.accountId,
416
+ mqttTopic,
417
+ );
353
418
  return;
354
419
  }
355
420
 
@@ -375,7 +440,14 @@ async function handleIncomingMessage(
375
440
  metadata: { mqttTopic, traceId },
376
441
  };
377
442
 
378
- log?.info(`[rol-websocket-channel] 📨 Received: "${normalizedMessage.text}" from ${normalizedMessage.senderId}`);
443
+ const targetAgentId: string | null = innerData.agentId ?? innerData.agent_id ?? null;
444
+ const targetSessionId: string | null = innerData.sessionKey ?? innerData.session_key ?? null;
445
+
446
+ log?.info(
447
+ `[rol-websocket-channel] 📨 Received: "${normalizedMessage.text}" from ${normalizedMessage.senderId}` +
448
+ (targetAgentId ? ` → agent:${targetAgentId}` : "") +
449
+ (targetSessionId ? ` → session:${targetSessionId}` : "")
450
+ );
379
451
 
380
452
  // 解析路由
381
453
  const route = runtime.channel.routing.resolveAgentRoute({
@@ -385,14 +457,18 @@ async function handleIncomingMessage(
385
457
  peer: { kind: "direct", id: normalizedMessage.senderId },
386
458
  });
387
459
 
460
+ // 用户传参覆盖自动路由结果
461
+ const resolvedAccountId = targetAgentId ?? route.accountId;
462
+ const resolvedSessionKey = targetSessionId ?? route.sessionKey;
463
+
388
464
  // 构建消息上下文
389
465
  const ctxPayload = runtime.channel.reply.finalizeInboundContext({
390
466
  Body: normalizedMessage.text,
391
467
  BodyForAgent: normalizedMessage.text,
392
468
  From: normalizedMessage.senderId,
393
469
  To: undefined,
394
- SessionKey: route.sessionKey,
395
- AccountId: route.accountId,
470
+ SessionKey: resolvedSessionKey,
471
+ AccountId: resolvedAccountId,
396
472
  ChatType: "direct",
397
473
  SenderName: normalizedMessage.senderName,
398
474
  SenderId: normalizedMessage.senderId,
@@ -408,7 +484,7 @@ async function handleIncomingMessage(
408
484
  cfg,
409
485
  dispatcherOptions: {
410
486
  deliver: async (payload: ReplyPayload) => {
411
- const conn = ConnectionManager.getConnection(account.accountId);
487
+ const conn = ConnectionManager.getGlobalConnection();
412
488
  if (!conn || !conn.ws || !conn.ws.connected) {
413
489
  throw new Error("No MQTT connection available");
414
490
  }
@@ -429,7 +505,9 @@ async function handleIncomingMessage(
429
505
  },
430
506
  });
431
507
  } catch (err) {
432
- log?.error(`[rol-websocket-channel] Failed to process message: ${err instanceof Error ? err.message : String(err)}`);
508
+ log?.error(
509
+ `[rol-websocket-channel] Failed to process message: ${err instanceof Error ? err.message : String(err)}`,
510
+ );
433
511
  }
434
512
  }
435
513
 
@@ -451,16 +529,20 @@ async function handleCustomMessageType(
451
529
  if (typeof handlerMethod === "function") {
452
530
  try {
453
531
  const methodResult = await handlerMethod.call(messageHandler, innerData);
454
-
532
+
455
533
  // 兼容两种返回格式:
456
534
  // 1. { ok, result, error } 格式(新的 admin 方法)
457
535
  // 2. 直接返回数据格式(原有的 ping、echo、status 方法)
458
- if (typeof methodResult === 'object' && methodResult !== null && 'ok' in methodResult) {
536
+ if (
537
+ typeof methodResult === "object" &&
538
+ methodResult !== null &&
539
+ "ok" in methodResult
540
+ ) {
459
541
  // 新格式:{ ok, result, error }
460
542
  response.success = methodResult.ok;
461
543
  response.data = methodResult.result;
462
544
  if (!methodResult.ok) {
463
- response.error = methodResult.error?.message || 'Unknown error';
545
+ response.error = methodResult.error?.message || "Unknown error";
464
546
  }
465
547
  } else {
466
548
  // 旧格式:直接返回数据
@@ -476,7 +558,7 @@ async function handleCustomMessageType(
476
558
  response.error = `Unknown message type: ${msgType}`;
477
559
  }
478
560
 
479
- const conn = ConnectionManager.getConnection(accountId);
561
+ const conn = ConnectionManager.getGlobalConnection();
480
562
  if (conn && conn.ws && conn.ws.connected) {
481
563
  conn.ws.publish(mqttTopic, JSON.stringify(response));
482
564
  }
@@ -496,13 +578,13 @@ export default function register(api: any) {
496
578
  isPluginRegistered = true;
497
579
 
498
580
  pluginRuntime = api.runtime;
499
-
581
+
500
582
  // 初始化共享 context
501
583
  initializeContext();
502
-
584
+
503
585
  // 注册 Channel
504
586
  api.registerChannel({ plugin: WebSocketChannel });
505
-
587
+
506
588
  // 注册 CLI(保留供外部调用)
507
589
  registerAdminBridgeCli(api);
508
590
  }
@@ -511,66 +593,168 @@ export default function register(api: any) {
511
593
  // 7. CLI 注册(保留供外部调用)
512
594
  // ============================================
513
595
  function registerAdminBridgeCli(api: any) {
514
- api.registerCli(({ program }: { program: any }) => {
515
- const root = program
516
- .command('admin-bridge')
517
- .description('OpenClaw admin bridge utilities')
518
- .addHelpText('after', () => '\nCommands return JSON to stdout for Python or shell orchestration.\n');
519
-
520
- // 这里可以添加 CLI 命令,但现在先保持简单
521
- // 如果需要完整的 CLI,可以从 openclaw-ts/index.ts 复制过来
522
-
523
- root
524
- .command('ping')
525
- .description('Test admin bridge connection')
526
- .action(async () => {
527
- try {
528
- const { ping } = await import('./src/admin/methods/system.js');
529
- const { getContext } = await import('./src/shared/context.js');
530
- const result = await ping(undefined, getContext());
531
- process.stdout.write(JSON.stringify({ ok: true, result }, null, 2) + '\n');
532
- } catch (error) {
533
- process.exitCode = 1;
534
- process.stderr.write(JSON.stringify({
535
- ok: false,
536
- error: { message: error instanceof Error ? error.message : String(error) }
537
- }, null, 2) + '\n');
538
- }
539
- });
540
-
541
- root
542
- .command('pair <key>')
543
- .description('Pair rol-websocket-channel and write OpenClaw configuration')
544
- .option('--endpoint <url>', 'Pair exchange endpoint')
545
- .option('--auth <token>', 'Authorization header value for pair exchange')
546
- .action(async (key: string, options: { endpoint?: string; auth?: string }) => {
547
- try {
548
- const { pairWithKey } = await import('./src/admin/methods/pairing.js');
549
- const result = await pairWithKey(
550
- {
551
- key,
552
- endpoint: options.endpoint,
553
- auth: options.auth
554
- },
555
- getContext()
556
- );
557
- process.stdout.write(JSON.stringify({ ok: true, result }, null, 2) + '\n');
558
- } catch (error) {
559
- process.exitCode = 1;
560
- process.stderr.write(JSON.stringify({
561
- ok: false,
562
- error: {
563
- message: error instanceof Error ? error.message : String(error),
564
- code: (error as any)?.data?.code
596
+ api.registerCli(
597
+ ({ program }: { program: any }) => {
598
+ const root = program
599
+ .command("admin-bridge")
600
+ .description("OpenClaw admin bridge utilities")
601
+ .addHelpText(
602
+ "after",
603
+ () =>
604
+ "\nCommands return JSON to stdout for Python or shell orchestration.\n",
605
+ );
606
+
607
+ // 这里可以添加 CLI 命令,但现在先保持简单
608
+ // 如果需要完整的 CLI,可以从 openclaw-ts/index.ts 复制过来
609
+
610
+ root
611
+ .command("ping")
612
+ .description("Test admin bridge connection")
613
+ .action(async () => {
614
+ try {
615
+ const { ping } = await import("./src/admin/methods/system.js");
616
+ const { getContext } = await import("./src/shared/context.js");
617
+ const result = await ping(undefined, getContext());
618
+ process.stdout.write(
619
+ JSON.stringify({ ok: true, result }, null, 2) + "\n",
620
+ );
621
+ } catch (error) {
622
+ process.exitCode = 1;
623
+ process.stderr.write(
624
+ JSON.stringify(
625
+ {
626
+ ok: false,
627
+ error: {
628
+ message:
629
+ error instanceof Error ? error.message : String(error),
630
+ },
631
+ },
632
+ null,
633
+ 2,
634
+ ) + "\n",
635
+ );
636
+ }
637
+ });
638
+
639
+ root
640
+ .command("pair <key>")
641
+ .description(
642
+ "Pair rol-websocket-channel and write OpenClaw configuration",
643
+ )
644
+ .option("--endpoint <url>", "Pair exchange endpoint")
645
+ .option(
646
+ "--auth <token>",
647
+ "Authorization header value for pair exchange",
648
+ )
649
+ .action(
650
+ async (
651
+ key: string,
652
+ options: { endpoint?: string; auth?: string },
653
+ ) => {
654
+ try {
655
+ const { pairWithKey } =
656
+ await import("./src/admin/methods/pairing.js");
657
+ const result = await pairWithKey(
658
+ {
659
+ key,
660
+ endpoint: options.endpoint,
661
+ auth: options.auth,
662
+ },
663
+ getContext(),
664
+ );
665
+ process.stdout.write(
666
+ JSON.stringify({ ok: true, result }, null, 2) + "\n",
667
+ );
668
+ } catch (error) {
669
+ process.exitCode = 1;
670
+ process.stderr.write(
671
+ JSON.stringify(
672
+ {
673
+ ok: false,
674
+ error: {
675
+ message:
676
+ error instanceof Error ? error.message : String(error),
677
+ code: (error as any)?.data?.code,
678
+ },
679
+ },
680
+ null,
681
+ 2,
682
+ ) + "\n",
683
+ );
565
684
  }
566
- }, null, 2) + '\n');
567
- }
568
- });
569
- }, {
570
- descriptors: [{
571
- name: 'admin-bridge',
572
- description: 'OpenClaw admin bridge commands',
573
- hasSubcommands: true
574
- }]
575
- });
685
+ },
686
+ );
687
+
688
+ const mem9 = root
689
+ .command("mem9")
690
+ .description("Mem9 installer and reconnect utilities");
691
+
692
+ mem9
693
+ .command("install")
694
+ .description("Install mem9 plugin, create cloud key, write config, and restart gateway")
695
+ .action(async () => {
696
+ try {
697
+ const { installMem9 } = await import("./src/admin/methods/mem9.js");
698
+ const result = await installMem9(getContext());
699
+ process.stdout.write(
700
+ JSON.stringify({ ok: true, result }, null, 2) + "\n",
701
+ );
702
+ } catch (error) {
703
+ process.exitCode = 1;
704
+ process.stderr.write(
705
+ JSON.stringify(
706
+ {
707
+ ok: false,
708
+ error: {
709
+ message:
710
+ error instanceof Error ? error.message : String(error),
711
+ code: (error as any)?.data?.code,
712
+ },
713
+ },
714
+ null,
715
+ 2,
716
+ ) + "\n",
717
+ );
718
+ }
719
+ });
720
+
721
+ mem9
722
+ .command("reconnect <key>")
723
+ .description("Replace mem9 apiKey, update config, and restart gateway")
724
+ .action(async (key: string) => {
725
+ try {
726
+ const { reconnectMem9 } = await import("./src/admin/methods/mem9.js");
727
+ const result = await reconnectMem9(key, getContext());
728
+ process.stdout.write(
729
+ JSON.stringify({ ok: true, result }, null, 2) + "\n",
730
+ );
731
+ } catch (error) {
732
+ process.exitCode = 1;
733
+ process.stderr.write(
734
+ JSON.stringify(
735
+ {
736
+ ok: false,
737
+ error: {
738
+ message:
739
+ error instanceof Error ? error.message : String(error),
740
+ code: (error as any)?.data?.code,
741
+ },
742
+ },
743
+ null,
744
+ 2,
745
+ ) + "\n",
746
+ );
747
+ }
748
+ });
749
+ },
750
+ {
751
+ descriptors: [
752
+ {
753
+ name: "admin-bridge",
754
+ description: "OpenClaw admin bridge commands",
755
+ hasSubcommands: true,
756
+ },
757
+ ],
758
+ },
759
+ );
576
760
  }
@@ -35,6 +35,7 @@ import {
35
35
  createMemoryBackupRecord,
36
36
  importMemoryZip,
37
37
  } from './src/admin/methods/memory.js';
38
+ import { getMem9Config, installMem9, reconnectMem9 } from './src/admin/methods/mem9.js';
38
39
  import { restart, stop, doctorFix, logs } from './src/admin/methods/system.js';
39
40
 
40
41
  export class MessageHandler {
@@ -356,6 +357,28 @@ export class MessageHandler {
356
357
  });
357
358
  }
358
359
 
360
+ async mem9Install(_data: any): Promise<any> {
361
+ return wrapAdminCall(async () => {
362
+ const context = getContext();
363
+ return await installMem9(context);
364
+ });
365
+ }
366
+
367
+ async mem9GetConfig(_data: any): Promise<any> {
368
+ return wrapAdminCall(async () => {
369
+ const context = getContext();
370
+ return await getMem9Config(context);
371
+ });
372
+ }
373
+
374
+ async mem9Reconnect(data: any): Promise<any> {
375
+ return wrapAdminCall(async () => {
376
+ const context = getContext();
377
+ const key = typeof data?.key === 'string' ? data.key : '';
378
+ return await reconnectMem9(key, context);
379
+ });
380
+ }
381
+
359
382
  /**
360
383
  * 重启 OpenClaw Gateway
361
384
  */
@@ -193,8 +193,8 @@
193
193
  },
194
194
  "channels.rol-websocket-channel.config.mqttUrl": {
195
195
  "label": "MQTT Broker URL",
196
- "placeholder": "ws://192.168.1.23:8083/mqtt",
197
- "help": "MQTT broker WebSocket URL (e.g., ws://192.168.1.23:8083/mqtt)"
196
+ "placeholder": "ws://192.168.1.152:8083/mqtt",
197
+ "help": "MQTT broker WebSocket URL (e.g., ws://192.168.1.152:8083/mqtt)"
198
198
  },
199
199
  "channels.rol-websocket-channel.config.mqttTopic": {
200
200
  "label": "MQTT Topic",