claw-subagent-service 0.0.77 → 0.0.80
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/package.json
CHANGED
|
@@ -11,6 +11,11 @@ const { RongyunMessageTypeEnum } = require('./rongyun-message-types');
|
|
|
11
11
|
const { OpenClawCommandEnum } = require('./openclaw-enum');
|
|
12
12
|
const { executeCommand } = require('./openclaw-control');
|
|
13
13
|
const { createOpencodeSession, deleteOpencodeSession, forwardChatMessage } = require('./opencode-service');
|
|
14
|
+
const { ServiceManager } = require('./service-manager');
|
|
15
|
+
const { collectDashboardData } = require('./dashboard-collector');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const os = require('os');
|
|
14
19
|
|
|
15
20
|
class RongyunMessageHandler {
|
|
16
21
|
constructor(rongcloudClient, config, log) {
|
|
@@ -72,6 +77,12 @@ class RongyunMessageHandler {
|
|
|
72
77
|
case RongyunMessageTypeEnum.DELETE_OPENCODE_SESSION:
|
|
73
78
|
await this.handleDeleteSession(parsed);
|
|
74
79
|
break;
|
|
80
|
+
case RongyunMessageTypeEnum.DEVICE_CONTROL:
|
|
81
|
+
await this.handleDeviceControl(parsed);
|
|
82
|
+
break;
|
|
83
|
+
case RongyunMessageTypeEnum.DEVICE_STATUS_REQUEST:
|
|
84
|
+
await this.handleDeviceStatusRequest(parsed);
|
|
85
|
+
break;
|
|
75
86
|
default:
|
|
76
87
|
this.logWarn(`未处理的消息类型: ${msgType}`);
|
|
77
88
|
}
|
|
@@ -85,8 +96,9 @@ class RongyunMessageHandler {
|
|
|
85
96
|
const command = data.command;
|
|
86
97
|
const commandId = data.command_id;
|
|
87
98
|
const requestId = data.request_id;
|
|
99
|
+
const sourceId = data.source_im_id;
|
|
88
100
|
|
|
89
|
-
this.logInfo(`[RongyunMessageHandler] 收到命令: command=${command}, command_id=${commandId}`);
|
|
101
|
+
this.logInfo(`[RongyunMessageHandler] 收到命令: command=${command}, command_id=${commandId}, from=${sourceId || 'guardserver'}`);
|
|
90
102
|
|
|
91
103
|
// 验证命令是否有效
|
|
92
104
|
const validCommands = Object.values(OpenClawCommandEnum);
|
|
@@ -96,7 +108,7 @@ class RongyunMessageHandler {
|
|
|
96
108
|
command_id: commandId,
|
|
97
109
|
status: 'error',
|
|
98
110
|
message: `未知命令: ${command}`
|
|
99
|
-
}, requestId);
|
|
111
|
+
}, requestId, sourceId);
|
|
100
112
|
return;
|
|
101
113
|
}
|
|
102
114
|
|
|
@@ -107,7 +119,7 @@ class RongyunMessageHandler {
|
|
|
107
119
|
command_id: commandId,
|
|
108
120
|
status: 'busy',
|
|
109
121
|
message: '正在执行上一个指令,请稍后再试'
|
|
110
|
-
}, requestId);
|
|
122
|
+
}, requestId, sourceId);
|
|
111
123
|
return;
|
|
112
124
|
}
|
|
113
125
|
|
|
@@ -119,7 +131,7 @@ class RongyunMessageHandler {
|
|
|
119
131
|
await this.sendResponse(RongyunMessageTypeEnum.COMMAND_RESULT, {
|
|
120
132
|
...response,
|
|
121
133
|
command_id: commandId
|
|
122
|
-
}, requestId);
|
|
134
|
+
}, requestId, sourceId);
|
|
123
135
|
});
|
|
124
136
|
} catch (e) {
|
|
125
137
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -129,7 +141,7 @@ class RongyunMessageHandler {
|
|
|
129
141
|
command_id: commandId,
|
|
130
142
|
status: 'error',
|
|
131
143
|
message: msg
|
|
132
|
-
}, requestId);
|
|
144
|
+
}, requestId, sourceId);
|
|
133
145
|
} finally {
|
|
134
146
|
this.commandLock = false;
|
|
135
147
|
}
|
|
@@ -229,15 +241,125 @@ class RongyunMessageHandler {
|
|
|
229
241
|
}
|
|
230
242
|
}
|
|
231
243
|
|
|
232
|
-
async
|
|
244
|
+
async handleDeviceControl(data) {
|
|
245
|
+
const command = data.command;
|
|
246
|
+
const requestId = data.request_id;
|
|
247
|
+
const targetId = data.source_im_id;
|
|
248
|
+
|
|
249
|
+
this.logInfo(`[RongyunMessageHandler] 收到设备控制命令: command=${command}, from=${targetId}`);
|
|
250
|
+
|
|
251
|
+
const validCommands = ['disable', 'enable', 'delete', 'status'];
|
|
252
|
+
if (!validCommands.includes(command)) {
|
|
253
|
+
await this.sendDeviceControlResult(targetId, requestId, command, 'error', `未知命令: ${command}`);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
let result;
|
|
259
|
+
switch (command) {
|
|
260
|
+
case 'disable': {
|
|
261
|
+
const svcMgr = new ServiceManager('claw-subagent-service', 'OpenClaw Guard CLI Client', process.argv[1], this.log);
|
|
262
|
+
await svcMgr.stop();
|
|
263
|
+
await svcMgr.uninstall();
|
|
264
|
+
result = { status: 'success', message: '设备服务已禁用' };
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
case 'enable': {
|
|
268
|
+
const svcMgr = new ServiceManager('claw-subagent-service', 'OpenClaw Guard CLI Client', process.argv[1], this.log);
|
|
269
|
+
await svcMgr.install();
|
|
270
|
+
result = { status: 'success', message: '设备服务已启用' };
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
case 'delete': {
|
|
274
|
+
const svcMgr = new ServiceManager('claw-subagent-service', 'OpenClaw Guard CLI Client', process.argv[1], this.log);
|
|
275
|
+
try { await svcMgr.stop(); } catch (e) {}
|
|
276
|
+
try { await svcMgr.uninstall(); } catch (e) {}
|
|
277
|
+
const homeDir = os.homedir();
|
|
278
|
+
const configPaths = [
|
|
279
|
+
path.join(homeDir, '.claw-bridge', 'config.json'),
|
|
280
|
+
path.join(__dirname, '..', '..', 'rongcloud-config.json')
|
|
281
|
+
];
|
|
282
|
+
for (const p of configPaths) {
|
|
283
|
+
if (fs.existsSync(p)) {
|
|
284
|
+
fs.unlinkSync(p);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
result = { status: 'success', message: '设备已删除,本地配置已清除' };
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case 'status': {
|
|
291
|
+
const dashboard = await collectDashboardData();
|
|
292
|
+
result = { status: 'success', message: '状态查询成功', data: dashboard };
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
await this.sendDeviceControlResult(targetId, requestId, command, result.status, result.message, result.data);
|
|
298
|
+
} catch (e) {
|
|
299
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
300
|
+
this.logError(`设备控制命令执行异常: ${msg}`);
|
|
301
|
+
await this.sendDeviceControlResult(targetId, requestId, command, 'error', msg);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async handleDeviceStatusRequest(data) {
|
|
306
|
+
const requestId = data.request_id;
|
|
307
|
+
const targetId = data.source_im_id;
|
|
308
|
+
|
|
309
|
+
this.logInfo(`[RongyunMessageHandler] 收到设备状态请求, from=${targetId}`);
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const dashboard = await collectDashboardData();
|
|
313
|
+
await this.sendDeviceStatusReport(targetId, requestId, dashboard);
|
|
314
|
+
} catch (e) {
|
|
315
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
316
|
+
this.logError(`设备状态查询异常: ${msg}`);
|
|
317
|
+
await this.sendDeviceStatusReport(targetId, requestId, null, msg);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async sendDeviceControlResult(targetId, requestId, command, status, message, data) {
|
|
322
|
+
if (!this.messageSender) {
|
|
323
|
+
this.logError('MessageSender 未设置,无法发送响应');
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
try {
|
|
327
|
+
await this.messageSender.sendDeviceControlResult(targetId, requestId, command, status, message, data);
|
|
328
|
+
this.logInfo(`设备控制结果已发送: ${command} -> ${targetId}`);
|
|
329
|
+
} catch (e) {
|
|
330
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
331
|
+
this.logError(`发送设备控制结果失败: ${msg}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async sendDeviceStatusReport(targetId, requestId, data, error) {
|
|
336
|
+
if (!this.messageSender) {
|
|
337
|
+
this.logError('MessageSender 未设置,无法发送响应');
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
await this.messageSender.sendDeviceStatusReport(targetId, requestId, data, error);
|
|
342
|
+
this.logInfo(`设备状态报告已发送 -> ${targetId}`);
|
|
343
|
+
} catch (e) {
|
|
344
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
345
|
+
this.logError(`发送设备状态报告失败: ${msg}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async sendResponse(msgType, content, requestId, targetId) {
|
|
233
350
|
if (!this.messageSender) {
|
|
234
351
|
this.logError('MessageSender 未设置,无法发送响应');
|
|
235
352
|
return;
|
|
236
353
|
}
|
|
237
354
|
|
|
238
355
|
try {
|
|
239
|
-
|
|
240
|
-
|
|
356
|
+
if (targetId) {
|
|
357
|
+
await this.messageSender.sendToTarget(targetId, msgType, content, requestId);
|
|
358
|
+
this.logInfo(`响应已发送: ${msgType} -> ${targetId}`);
|
|
359
|
+
} else {
|
|
360
|
+
await this.messageSender.sendProtocolMessage(msgType, content, requestId);
|
|
361
|
+
this.logInfo(`响应已发送: ${msgType}`);
|
|
362
|
+
}
|
|
241
363
|
} catch (e) {
|
|
242
364
|
const msg = e instanceof Error ? e.message : String(e);
|
|
243
365
|
this.logError(`发送响应失败: ${msg}`);
|
|
@@ -150,6 +150,86 @@ class RongyunMessageSender {
|
|
|
150
150
|
...data,
|
|
151
151
|
});
|
|
152
152
|
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 发送消息到指定目标(P2P)
|
|
156
|
+
*/
|
|
157
|
+
async sendToTarget(targetId, msgType, content, requestId) {
|
|
158
|
+
if (!this.rongcloudClient?.isConnected) {
|
|
159
|
+
this.log?.error('[RongyunMessageSender] 未连接,无法发送消息');
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
const mac = getMacAddress();
|
|
165
|
+
const secret = generateSecret(mac, this.config.secretKey || 'secret_key');
|
|
166
|
+
const messagePayload = {
|
|
167
|
+
msg_type: msgType,
|
|
168
|
+
source_im_id: this.config.accountId || '',
|
|
169
|
+
destination_im_id: targetId,
|
|
170
|
+
mac: mac,
|
|
171
|
+
secret: secret,
|
|
172
|
+
content: typeof content === 'string' ? content : JSON.stringify(content),
|
|
173
|
+
request_id: requestId || '',
|
|
174
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// 优先使用自定义消息类型发送 P2P 消息
|
|
178
|
+
let result;
|
|
179
|
+
if (this.rongcloudClient.SystemServiceMessage) {
|
|
180
|
+
result = await this.rongcloudClient.sendCustomMessage(
|
|
181
|
+
targetId,
|
|
182
|
+
messagePayload,
|
|
183
|
+
1 // PRIVATE
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
// 回退到文本消息(兼容旧版本)
|
|
187
|
+
result = await this.rongcloudClient.sendMessage(
|
|
188
|
+
targetId,
|
|
189
|
+
JSON.stringify(messagePayload),
|
|
190
|
+
1 // PRIVATE
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return result;
|
|
195
|
+
} catch (err) {
|
|
196
|
+
this.log?.error(`[RongyunMessageSender] P2P发送异常: ${err.message}`);
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 发送设备控制结果(P2P)
|
|
203
|
+
*/
|
|
204
|
+
async sendDeviceControlResult(targetId, requestId, command, status, message, data) {
|
|
205
|
+
return await this.sendToTarget(
|
|
206
|
+
targetId,
|
|
207
|
+
RongyunMessageTypeEnum.DEVICE_CONTROL_RESULT,
|
|
208
|
+
{
|
|
209
|
+
command,
|
|
210
|
+
status,
|
|
211
|
+
message,
|
|
212
|
+
data
|
|
213
|
+
},
|
|
214
|
+
requestId
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 发送设备状态报告(P2P)
|
|
220
|
+
*/
|
|
221
|
+
async sendDeviceStatusReport(targetId, requestId, data, error) {
|
|
222
|
+
return await this.sendToTarget(
|
|
223
|
+
targetId,
|
|
224
|
+
RongyunMessageTypeEnum.DEVICE_STATUS_REPORT,
|
|
225
|
+
{
|
|
226
|
+
status: error ? 'error' : 'success',
|
|
227
|
+
message: error || '状态报告',
|
|
228
|
+
data
|
|
229
|
+
},
|
|
230
|
+
requestId
|
|
231
|
+
);
|
|
232
|
+
}
|
|
153
233
|
}
|
|
154
234
|
|
|
155
235
|
module.exports = {
|
|
@@ -22,7 +22,11 @@ const RongyunMessageTypeEnum = {
|
|
|
22
22
|
CHAT_MESSAGE: "chat_message",
|
|
23
23
|
CREATE_OPENCODE_SESSION: "create_opencode_session",
|
|
24
24
|
OPENCODE_SESSION_CREATED: "opencode_session_created",
|
|
25
|
-
DELETE_OPENCODE_SESSION: "delete_opencode_session"
|
|
25
|
+
DELETE_OPENCODE_SESSION: "delete_opencode_session",
|
|
26
|
+
DEVICE_CONTROL: "device_control",
|
|
27
|
+
DEVICE_CONTROL_RESULT: "device_control_result",
|
|
28
|
+
DEVICE_STATUS_REQUEST: "device_status_request",
|
|
29
|
+
DEVICE_STATUS_REPORT: "device_status_report"
|
|
26
30
|
};
|
|
27
31
|
|
|
28
32
|
module.exports = { RongyunMessageTypeEnum };
|
|
@@ -39,6 +39,18 @@ class RongCloudClient {
|
|
|
39
39
|
this.log?.info('[RongCloudClient] 初始化 SDK...');
|
|
40
40
|
RongIMLib.init({ appkey: this.config.appKey });
|
|
41
41
|
|
|
42
|
+
// 注册 system_service 自定义消息类型(与前端对齐)
|
|
43
|
+
try {
|
|
44
|
+
if (typeof RongIMLib.registerMessageType === 'function') {
|
|
45
|
+
this.SystemServiceMessage = RongIMLib.registerMessageType('system_service', true, false);
|
|
46
|
+
this.log?.info('[RongCloudClient] system_service 自定义消息类型已注册');
|
|
47
|
+
} else {
|
|
48
|
+
this.log?.warn('[RongCloudClient] SDK 不支持 registerMessageType');
|
|
49
|
+
}
|
|
50
|
+
} catch (err) {
|
|
51
|
+
this.log?.warn(`[RongCloudClient] 注册 system_service 消息类型失败: ${err.message}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
this.log?.info(`[RongCloudClient] SDK Events: ${JSON.stringify(Object.keys(RongIMLib.Events || {}))}`);
|
|
43
55
|
this.log?.info(`[RongCloudClient] has addEventListener: ${typeof RongIMLib.addEventListener === 'function'}`);
|
|
44
56
|
this.log?.info(`[RongCloudClient] has sendReadReceiptMessage: ${typeof RongIMLib.sendReadReceiptMessage === 'function'}`);
|
|
@@ -257,6 +269,50 @@ class RongCloudClient {
|
|
|
257
269
|
}
|
|
258
270
|
}
|
|
259
271
|
|
|
272
|
+
async sendCustomMessage(targetId, content, conversationType, customType = 'system_service') {
|
|
273
|
+
if (!this.isConnected) {
|
|
274
|
+
this.log?.error('[RongCloudClient] 未连接,无法发送自定义消息');
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (!this.SystemServiceMessage) {
|
|
279
|
+
this.log?.error('[RongCloudClient] system_service 消息类型未注册');
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
const convType = conversationType === ConversationType.GROUP
|
|
285
|
+
? (RongIMLib.ConversationType?.GROUP || ConversationType.GROUP)
|
|
286
|
+
: (RongIMLib.ConversationType?.PRIVATE || ConversationType.PRIVATE);
|
|
287
|
+
|
|
288
|
+
const messageContent = typeof content === 'string' ? JSON.parse(content) : content;
|
|
289
|
+
const customMsg = new this.SystemServiceMessage(messageContent);
|
|
290
|
+
|
|
291
|
+
const result = await RongIMLib.sendMessage(
|
|
292
|
+
{ conversationType: convType, targetId },
|
|
293
|
+
customMsg
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
if (result.code === 0 || result.code === 200) {
|
|
297
|
+
const sentUId = result.data?.messageUId;
|
|
298
|
+
if (sentUId) {
|
|
299
|
+
this.sentMessageUIds.add(sentUId);
|
|
300
|
+
if (this.sentMessageUIds.size > this.sentMessageDedupMaxSize) {
|
|
301
|
+
const first = this.sentMessageUIds.values().next().value;
|
|
302
|
+
this.sentMessageUIds.delete(first);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return true;
|
|
306
|
+
} else {
|
|
307
|
+
this.log?.error(`[RongCloudClient] 自定义消息发送失败, code: ${result.code}`);
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
this.log?.error(`[RongCloudClient] 发送自定义消息异常: ${err.message}`);
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
260
316
|
async sendReadReceipt(msg) {
|
|
261
317
|
if (!this.isConnected) {
|
|
262
318
|
this.log?.warn('[RongCloudClient] 未连接,无法发送已读回执');
|