claw-subagent-service 0.0.177 → 0.0.179
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
|
@@ -78,12 +78,72 @@ class RongyunMessageHandler {
|
|
|
78
78
|
this.commandLock = false;
|
|
79
79
|
this.commandLockTimer = null;
|
|
80
80
|
this.messageSender = null;
|
|
81
|
+
this.serverAPI = null;
|
|
82
|
+
// 流式消息队列:确保片段串行发送
|
|
83
|
+
this._streamQueue = Promise.resolve();
|
|
84
|
+
// 存储流式消息的 RongCloud messageUID:streamId -> messageUID
|
|
85
|
+
this._streamMessageUIDs = new Map();
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
setMessageSender(messageSender) {
|
|
84
89
|
this.messageSender = messageSender;
|
|
85
90
|
}
|
|
86
91
|
|
|
92
|
+
setServerAPI(serverAPI) {
|
|
93
|
+
this.serverAPI = serverAPI;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 发送流式消息片段(直接调用融云 API)
|
|
98
|
+
* @param {string} fromUserId - 发送者ID
|
|
99
|
+
* @param {string} targetId - 目标用户ID
|
|
100
|
+
* @param {string} content - 消息内容
|
|
101
|
+
* @param {string} streamId - 流式消息ID
|
|
102
|
+
* @param {boolean} isFirstChunk - 是否首流
|
|
103
|
+
* @param {boolean} isLastChunk - 是否尾流
|
|
104
|
+
* @param {number} seq - 片段序号
|
|
105
|
+
*/
|
|
106
|
+
async _sendStreamChunk(fromUserId, targetId, content, streamId, isFirstChunk, isLastChunk, seq = 1) {
|
|
107
|
+
const contentPreview = typeof content === 'string' ? content.substring(0, 100) : JSON.stringify(content).substring(0, 100);
|
|
108
|
+
this.logInfo(`[RongyunMessageHandler] _sendStreamChunk: target=${targetId}, streamId=${streamId}, seq=${seq}, first=${isFirstChunk}, last=${isLastChunk}, content_len=${content?.length || 0}`);
|
|
109
|
+
|
|
110
|
+
if (!this.serverAPI) {
|
|
111
|
+
this.logWarn('[RongyunMessageHandler] _sendStreamChunk skipped: serverAPI not configured');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 使用队列确保流式消息片段串行发送,避免并发导致后端处理错乱
|
|
116
|
+
this._streamQueue = this._streamQueue.then(async () => {
|
|
117
|
+
try {
|
|
118
|
+
// 获取已存储的 RongCloud messageUID(首流响应返回的)
|
|
119
|
+
const messageUID = this._streamMessageUIDs.get(streamId);
|
|
120
|
+
|
|
121
|
+
const result = await this.serverAPI.sendStreamPrivate({
|
|
122
|
+
fromUserId,
|
|
123
|
+
toUserId: targetId,
|
|
124
|
+
content,
|
|
125
|
+
streamId,
|
|
126
|
+
isFirstChunk,
|
|
127
|
+
isLastChunk,
|
|
128
|
+
seq,
|
|
129
|
+
messageUID
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// 首流时存储 RongCloud 返回的 messageUID
|
|
133
|
+
if (isFirstChunk && result?.messageUID) {
|
|
134
|
+
this._streamMessageUIDs.set(streamId, result.messageUID);
|
|
135
|
+
this.logInfo(`[RongyunMessageHandler] 首流 messageUID 已存储: ${result.messageUID}, streamId=${streamId}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.logInfo(`[RongyunMessageHandler] _sendStreamChunk 成功: seq=${seq}`);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
this.logWarn(`[RongyunMessageHandler] 发送流式消息失败: ${err.message}, seq=${seq}`);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
await this._streamQueue;
|
|
145
|
+
}
|
|
146
|
+
|
|
87
147
|
logInfo(message) {
|
|
88
148
|
if (this.log?.info) {
|
|
89
149
|
this.log.info(message);
|
|
@@ -312,11 +372,32 @@ class RongyunMessageHandler {
|
|
|
312
372
|
}
|
|
313
373
|
|
|
314
374
|
let fullResponse = '';
|
|
375
|
+
let buffer = ''; // 用于流式发送的缓冲区
|
|
315
376
|
const chatTimeoutMs = (this.config.chatTimeout || 600) * 1000;
|
|
377
|
+
|
|
378
|
+
// 生成流式消息唯一ID
|
|
379
|
+
const streamId = `stream-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
380
|
+
let seq = 0;
|
|
381
|
+
let hasSentChunk = false;
|
|
382
|
+
const fromUserId = this.config.accountId || '';
|
|
316
383
|
|
|
317
384
|
try {
|
|
318
385
|
await forwardChatMessage(sessionId, content, async (delta) => {
|
|
319
386
|
fullResponse += delta;
|
|
387
|
+
buffer += delta;
|
|
388
|
+
|
|
389
|
+
// 当缓冲区达到一定大小或包含标点时,发送流式片段
|
|
390
|
+
// 使用 50 字符作为触发阈值(与 message-handler.js 保持一致)
|
|
391
|
+
if (buffer.length >= 50) {
|
|
392
|
+
seq += 1;
|
|
393
|
+
const chunkToSend = buffer;
|
|
394
|
+
buffer = ''; // 清空缓冲区
|
|
395
|
+
|
|
396
|
+
// 首流时发送首流标记
|
|
397
|
+
const isFirstChunk = seq === 1;
|
|
398
|
+
await this._sendStreamChunk(fromUserId, sourceId, chunkToSend, streamId, isFirstChunk, false, seq);
|
|
399
|
+
hasSentChunk = true;
|
|
400
|
+
}
|
|
320
401
|
}, (level, message) => {
|
|
321
402
|
if (level === 'ERROR') {
|
|
322
403
|
this.logError(`[CHAT-API] ${message}`);
|
|
@@ -327,21 +408,50 @@ class RongyunMessageHandler {
|
|
|
327
408
|
}
|
|
328
409
|
}, chatTimeoutMs);
|
|
329
410
|
|
|
411
|
+
// 发送剩余缓冲区内容
|
|
412
|
+
if (buffer.length > 0) {
|
|
413
|
+
seq += 1;
|
|
414
|
+
const isFirstChunk = seq === 1;
|
|
415
|
+
await this._sendStreamChunk(fromUserId, sourceId, buffer, streamId, isFirstChunk, false, seq);
|
|
416
|
+
hasSentChunk = true;
|
|
417
|
+
buffer = '';
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// 发送尾流标记
|
|
421
|
+
if (hasSentChunk) {
|
|
422
|
+
seq += 1;
|
|
423
|
+
await this._sendStreamChunk(fromUserId, sourceId, '', streamId, false, true, seq);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// 同时发送完整的 command 消息作为历史记录(兼容旧前端)
|
|
330
427
|
await this.sendResponse(RongyunMessageTypeEnum.CHAT_MESSAGE, {
|
|
331
428
|
status: 'success',
|
|
332
429
|
message: 'Response received',
|
|
333
430
|
content: fullResponse,
|
|
334
431
|
metadata: {}
|
|
335
432
|
}, requestId, sourceId);
|
|
433
|
+
|
|
434
|
+
// 清理已存储的 messageUID
|
|
435
|
+
this._streamMessageUIDs.delete(streamId);
|
|
336
436
|
} catch (e) {
|
|
337
437
|
const msg = e instanceof Error ? e.message : String(e);
|
|
338
438
|
this.logError(`聊天消息处理异常: ${msg}`);
|
|
439
|
+
|
|
440
|
+
// 如果已经开始流式发送,发送错误标记
|
|
441
|
+
if (hasSentChunk) {
|
|
442
|
+
seq += 1;
|
|
443
|
+
await this._sendStreamChunk(fromUserId, sourceId, `[错误] 转发失败: ${msg}`, streamId, false, true, seq);
|
|
444
|
+
}
|
|
445
|
+
|
|
339
446
|
await this.sendResponse(RongyunMessageTypeEnum.CHAT_MESSAGE, {
|
|
340
447
|
status: 'error',
|
|
341
448
|
message: msg,
|
|
342
449
|
content: `[错误] 转发失败: ${msg}`,
|
|
343
450
|
metadata: {}
|
|
344
451
|
}, requestId, sourceId);
|
|
452
|
+
|
|
453
|
+
// 清理已存储的 messageUID
|
|
454
|
+
this._streamMessageUIDs.delete(streamId);
|
|
345
455
|
}
|
|
346
456
|
}
|
|
347
457
|
|
|
@@ -176,9 +176,9 @@ class RongyunMessageSender {
|
|
|
176
176
|
|
|
177
177
|
// 优先使用自定义消息类型发送 P2P 消息
|
|
178
178
|
let result;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
//
|
|
179
|
+
// 客服消息和聊天消息都将业务内容放在顶层,方便前端解析
|
|
180
|
+
if (this.rongcloudClient.ServiceChatMessage && (msgType.includes('service') || msgType === 'chat_message')) {
|
|
181
|
+
// 对于客服/聊天消息,直接将业务内容放在顶层,方便前端解析
|
|
182
182
|
const serviceChatPayload = {
|
|
183
183
|
msg_type: msgType,
|
|
184
184
|
...content, // 展开业务内容(status, content, sessionId, userId 等)
|
|
@@ -245,6 +245,65 @@ class RongyunMessageSender {
|
|
|
245
245
|
requestId
|
|
246
246
|
);
|
|
247
247
|
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 发送流式消息片段(P2P)
|
|
251
|
+
* 使用融云服务端API发送 RC:StreamMsg
|
|
252
|
+
* @param {Object} options - 流式消息选项
|
|
253
|
+
* @param {string} options.targetId - 目标用户ID
|
|
254
|
+
* @param {string} options.content - 消息片段内容
|
|
255
|
+
* @param {string} options.streamId - 流式消息ID
|
|
256
|
+
* @param {number} options.seq - 片段序号
|
|
257
|
+
* @param {boolean} options.isFirstChunk - 是否首流
|
|
258
|
+
* @param {boolean} options.isLastChunk - 是否尾流
|
|
259
|
+
* @param {string} options.messageUID - 首流返回的messageUID(后续流使用)
|
|
260
|
+
* @returns {Promise<Object>} 发送结果
|
|
261
|
+
*/
|
|
262
|
+
async sendStreamToTarget({
|
|
263
|
+
targetId,
|
|
264
|
+
content,
|
|
265
|
+
streamId,
|
|
266
|
+
seq = 1,
|
|
267
|
+
isFirstChunk = false,
|
|
268
|
+
isLastChunk = false,
|
|
269
|
+
messageUID = null
|
|
270
|
+
}) {
|
|
271
|
+
// 需要 serverAPI 支持
|
|
272
|
+
if (!this.serverAPI) {
|
|
273
|
+
this.log?.error('[RongyunMessageSender] serverAPI 未设置,无法发送流式消息');
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const fromUserId = this.config.accountId || '';
|
|
279
|
+
|
|
280
|
+
const result = await this.serverAPI.sendStreamPrivate({
|
|
281
|
+
fromUserId,
|
|
282
|
+
toUserId: targetId,
|
|
283
|
+
content,
|
|
284
|
+
streamId,
|
|
285
|
+
isFirstChunk,
|
|
286
|
+
isLastChunk,
|
|
287
|
+
seq,
|
|
288
|
+
streamType: 'text',
|
|
289
|
+
messageUID
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
this.log?.info(`[RongyunMessageSender] 流式消息已发送: seq=${seq}, first=${isFirstChunk}, last=${isLastChunk}`);
|
|
293
|
+
return result;
|
|
294
|
+
} catch (err) {
|
|
295
|
+
this.log?.error(`[RongyunMessageSender] 发送流式消息失败: ${err.message}`);
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 设置 serverAPI(用于发送流式消息)
|
|
302
|
+
* @param {RongCloudServerAPI} serverAPI
|
|
303
|
+
*/
|
|
304
|
+
setServerAPI(serverAPI) {
|
|
305
|
+
this.serverAPI = serverAPI;
|
|
306
|
+
}
|
|
248
307
|
}
|
|
249
308
|
|
|
250
309
|
module.exports = {
|
package/service/worker.js
CHANGED
|
@@ -8,6 +8,8 @@ const { createLogger } = require('./logger');
|
|
|
8
8
|
const { RongCloudClient, MessageHandler, ensurePluginsAllow } = require('./rongcloud');
|
|
9
9
|
const { RongyunMessageHandler } = require('./modules/rongyun-message-handler');
|
|
10
10
|
const { RongyunMessageSender } = require('./modules/rongyun-message-sender');
|
|
11
|
+
const { RongCloudServerAPI } = require('./rongcloud/rongcloud-server-api');
|
|
12
|
+
const { SystemConfigManager } = require('./modules/system-config');
|
|
11
13
|
const { HeartbeatManager, DashboardReporter } = require('./modules/heartbeat-dashboard');
|
|
12
14
|
const { getOpenClawStatus } = require('./modules/port-checker');
|
|
13
15
|
const { getMacAddress } = require('./modules/mac-address');
|
|
@@ -340,12 +342,20 @@ async function initRongCloud() {
|
|
|
340
342
|
|
|
341
343
|
rongcloudClient = new RongCloudClient(rongcloudConfig, log);
|
|
342
344
|
|
|
345
|
+
// 创建系统配置管理器(用于融云服务端API)
|
|
346
|
+
const configManager = new SystemConfigManager(rongcloudConfig, log);
|
|
347
|
+
|
|
348
|
+
// 创建融云服务端API客户端(用于发送流式消息)
|
|
349
|
+
const serverAPI = new RongCloudServerAPI(configManager, log);
|
|
350
|
+
|
|
343
351
|
// 创建消息发送器
|
|
344
352
|
const messageSender = new RongyunMessageSender(rongcloudClient, rongcloudConfig, log);
|
|
353
|
+
messageSender.setServerAPI(serverAPI); // 注入 serverAPI,支持发送流式消息
|
|
345
354
|
|
|
346
355
|
// 创建新的融云消息处理器(与桌面客户端对齐)
|
|
347
356
|
const rongyunMessageHandler = new RongyunMessageHandler(rongcloudClient, rongcloudConfig, log);
|
|
348
357
|
rongyunMessageHandler.setMessageSender(messageSender);
|
|
358
|
+
rongyunMessageHandler.setServerAPI(serverAPI); // 注入 serverAPI
|
|
349
359
|
|
|
350
360
|
messageHandler = new MessageHandler(
|
|
351
361
|
rongcloudConfig,
|