rol-websocket-channel 1.0.1 → 1.0.5
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 +245 -123
- package/openclaw.plugin.json +2 -2
- package/package.json +4 -5
- package/src/admin/methods/pairing.ts +1 -4
- package/src/mqtt/connection-manager.ts +262 -188
- package/src/mqtt/index.ts +6 -6
- package/src/mqtt/mqtt-client.ts +170 -119
- package/src/mqtt/mqtt.test.ts +670 -0
- package/src/mqtt/types.ts +46 -36
- package/bin/rol.js +0 -35
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 {
|
|
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: {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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.
|
|
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": {
|
|
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.
|
|
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)
|
|
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
|
|
212
|
-
const conn = ConnectionManager.
|
|
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
|
|
218
|
-
|
|
219
|
-
|
|
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
|
|
224
|
-
const conn = ConnectionManager.
|
|
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
|
|
230
|
-
|
|
231
|
-
|
|
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(
|
|
274
|
+
log?.info(
|
|
275
|
+
`[rol-websocket-channel] Starting MQTT Channel for ${account.accountId}`,
|
|
276
|
+
);
|
|
243
277
|
|
|
244
278
|
// 检查是否已有活跃连接,防止重复启动
|
|
245
|
-
if (ConnectionManager.
|
|
246
|
-
console.log(
|
|
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(
|
|
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.
|
|
293
|
+
const mqttUrl = account.mqttUrl || "ws://192.168.1.152:8083/mqtt";
|
|
256
294
|
const mqttTopic = account.mqttTopic || "announcement/tester";
|
|
257
|
-
console.log(
|
|
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(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
298
|
-
|
|
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(
|
|
356
|
+
console.log(
|
|
357
|
+
`[MQTT] startAccount(${account.accountId}): calling GlobalMqttClient.connect()...`,
|
|
358
|
+
);
|
|
304
359
|
await client.connect();
|
|
305
|
-
console.log(
|
|
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(
|
|
312
|
-
|
|
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(
|
|
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
|
-
|
|
443
|
+
const targetAgentId: string | null = innerData.agentId ?? innerData.agent_id ?? null;
|
|
444
|
+
const targetSessionId: string | null = innerData.sessionId ?? innerData.session_id ?? 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:
|
|
395
|
-
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.
|
|
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(
|
|
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 (
|
|
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 ||
|
|
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.
|
|
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,106 @@ export default function register(api: any) {
|
|
|
511
593
|
// 7. CLI 注册(保留供外部调用)
|
|
512
594
|
// ============================================
|
|
513
595
|
function registerAdminBridgeCli(api: any) {
|
|
514
|
-
api.registerCli(
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
},
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
685
|
+
},
|
|
686
|
+
);
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
descriptors: [
|
|
690
|
+
{
|
|
691
|
+
name: "admin-bridge",
|
|
692
|
+
description: "OpenClaw admin bridge commands",
|
|
693
|
+
hasSubcommands: true,
|
|
694
|
+
},
|
|
695
|
+
],
|
|
696
|
+
},
|
|
697
|
+
);
|
|
576
698
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
197
|
-
"help": "MQTT broker WebSocket URL (e.g., ws://192.168.1.
|
|
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",
|
package/package.json
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rol-websocket-channel",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "Unified OpenClaw plugin: MQTT Channel + Admin Bridge for remote management",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "nixgnehc",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"bin": {
|
|
9
|
-
"rol": "./bin/rol.js"
|
|
10
|
-
},
|
|
11
8
|
"keywords": [
|
|
12
9
|
"openclaw",
|
|
13
10
|
"mqtt",
|
|
@@ -18,13 +15,15 @@
|
|
|
18
15
|
],
|
|
19
16
|
"scripts": {
|
|
20
17
|
"build": "tsc",
|
|
21
|
-
"dev": "tsx watch index.ts"
|
|
18
|
+
"dev": "tsx watch index.ts",
|
|
19
|
+
"test": "node --import tsx/esm --test src/mqtt/mqtt.test.ts"
|
|
22
20
|
},
|
|
23
21
|
"dependencies": {
|
|
24
22
|
"mqtt": "^5.0.3"
|
|
25
23
|
},
|
|
26
24
|
"devDependencies": {
|
|
27
25
|
"@types/node": "^22.0.0",
|
|
26
|
+
"tsx": "^4.21.0",
|
|
28
27
|
"typescript": "^5.0.0"
|
|
29
28
|
},
|
|
30
29
|
"openclaw": {
|
|
@@ -101,10 +101,8 @@ async function exchangePairKey(
|
|
|
101
101
|
existingMqttUrl?: string | null
|
|
102
102
|
): Promise<PairingPayload> {
|
|
103
103
|
const endpoint = pickString(endpointOverride)
|
|
104
|
-
?? pickString(process.env.ROL_PAIR_ENDPOINT)
|
|
105
104
|
?? DEFAULT_PAIR_ENDPOINT;
|
|
106
|
-
const auth = pickString(authOverride)
|
|
107
|
-
?? pickString(process.env.ROL_PAIR_AUTH);
|
|
105
|
+
const auth = pickString(authOverride);
|
|
108
106
|
|
|
109
107
|
const headers: Record<string, string> = {
|
|
110
108
|
'Content-Type': 'application/json'
|
|
@@ -156,7 +154,6 @@ function normalizePairingPayload(
|
|
|
156
154
|
|
|
157
155
|
const mqttUrl = pickString(channelValue.mqttUrl)
|
|
158
156
|
?? pickString(root.mqttUrl)
|
|
159
|
-
?? pickString(process.env.ROL_PAIR_MQTT_URL)
|
|
160
157
|
?? existingMqttUrl
|
|
161
158
|
?? null;
|
|
162
159
|
if (!mqttUrl) {
|