claw-subagent-service 0.0.76 → 0.0.79
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,75 @@ 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
|
+
const result = await this.rongcloudClient.sendMessage(
|
|
178
|
+
targetId,
|
|
179
|
+
JSON.stringify(messagePayload),
|
|
180
|
+
1 // PRIVATE
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
return result;
|
|
184
|
+
} catch (err) {
|
|
185
|
+
this.log?.error(`[RongyunMessageSender] P2P发送异常: ${err.message}`);
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 发送设备控制结果(P2P)
|
|
192
|
+
*/
|
|
193
|
+
async sendDeviceControlResult(targetId, requestId, command, status, message, data) {
|
|
194
|
+
return await this.sendToTarget(
|
|
195
|
+
targetId,
|
|
196
|
+
RongyunMessageTypeEnum.DEVICE_CONTROL_RESULT,
|
|
197
|
+
{
|
|
198
|
+
command,
|
|
199
|
+
status,
|
|
200
|
+
message,
|
|
201
|
+
data
|
|
202
|
+
},
|
|
203
|
+
requestId
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* 发送设备状态报告(P2P)
|
|
209
|
+
*/
|
|
210
|
+
async sendDeviceStatusReport(targetId, requestId, data, error) {
|
|
211
|
+
return await this.sendToTarget(
|
|
212
|
+
targetId,
|
|
213
|
+
RongyunMessageTypeEnum.DEVICE_STATUS_REPORT,
|
|
214
|
+
{
|
|
215
|
+
status: error ? 'error' : 'success',
|
|
216
|
+
message: error || '状态报告',
|
|
217
|
+
data
|
|
218
|
+
},
|
|
219
|
+
requestId
|
|
220
|
+
);
|
|
221
|
+
}
|
|
153
222
|
}
|
|
154
223
|
|
|
155
224
|
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 };
|
|
@@ -369,17 +369,17 @@ class MessageHandler {
|
|
|
369
369
|
|
|
370
370
|
this.log?.info(`[MessageHandler] 流式消息完成,streamId=${streamId}, 总长度: ${fullText.length}`);
|
|
371
371
|
|
|
372
|
-
// 清理已存储的 messageUID,防止内存泄漏
|
|
373
|
-
this._streamMessageUIDs.delete(streamId);
|
|
374
|
-
|
|
375
372
|
// 发送持久化的普通文本消息作为历史记录(融云会保存 RC:TxtMsg)
|
|
376
373
|
if (buffer.trim()) {
|
|
377
374
|
try {
|
|
378
|
-
|
|
375
|
+
// 使用融云首流返回的 messageUID 作为 streamId,确保与前端匹配
|
|
376
|
+
const rongCloudMsgUID = this._streamMessageUIDs.get(streamId);
|
|
377
|
+
const historyStreamId = rongCloudMsgUID || streamId;
|
|
378
|
+
this.log?.info(`[MessageHandler] 发送历史记录文本消息: length=${buffer.length}, streamId=${historyStreamId}`);
|
|
379
379
|
// 使用 JSON 格式包含 streamId,前端可据此关联并更新流式消息内容
|
|
380
380
|
const historyContent = JSON.stringify({
|
|
381
381
|
__stream_history__: true,
|
|
382
|
-
streamId:
|
|
382
|
+
streamId: historyStreamId,
|
|
383
383
|
text: buffer,
|
|
384
384
|
sentTime: Date.now()
|
|
385
385
|
});
|
|
@@ -388,6 +388,9 @@ class MessageHandler {
|
|
|
388
388
|
this.log?.error(`[MessageHandler] 发送历史记录失败: ${err.message}`);
|
|
389
389
|
}
|
|
390
390
|
}
|
|
391
|
+
|
|
392
|
+
// 清理已存储的 messageUID,防止内存泄漏
|
|
393
|
+
this._streamMessageUIDs.delete(streamId);
|
|
391
394
|
}
|
|
392
395
|
);
|
|
393
396
|
} catch (err) {
|