@eyeclaw/eyeclaw 2.3.10 → 2.3.12
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 +1 -1
- package/src/websocket-client.ts +47 -3
package/package.json
CHANGED
package/src/websocket-client.ts
CHANGED
|
@@ -27,6 +27,7 @@ export class EyeClawWebSocketClient {
|
|
|
27
27
|
private subscribed = false
|
|
28
28
|
private pingInterval: any = null
|
|
29
29
|
private chunkSequence = 0 // 每个会话的 chunk 序号
|
|
30
|
+
private accumulatedContent = '' // 累积完整内容用于兜底
|
|
30
31
|
|
|
31
32
|
constructor(api: OpenClawPluginApi, config: EyeClawConfig, getState: () => any) {
|
|
32
33
|
this.api = api
|
|
@@ -104,9 +105,9 @@ export class EyeClawWebSocketClient {
|
|
|
104
105
|
return
|
|
105
106
|
}
|
|
106
107
|
|
|
107
|
-
// Ping/pong (协议级别的 ping
|
|
108
|
+
// Ping/pong (WebSocket 协议级别的 ping 由浏览器自动响应,无需手动处理)
|
|
108
109
|
if (message.type === 'ping') {
|
|
109
|
-
this.
|
|
110
|
+
this.api.logger.debug('[EyeClaw] Received protocol-level ping (auto-handled by WebSocket)')
|
|
110
111
|
return
|
|
111
112
|
}
|
|
112
113
|
|
|
@@ -244,6 +245,9 @@ export class EyeClawWebSocketClient {
|
|
|
244
245
|
if (done) {
|
|
245
246
|
// 流结束,通知 Rails
|
|
246
247
|
this.sendMessage('stream_end', { session_id: sessionId })
|
|
248
|
+
|
|
249
|
+
// 发送 stream_summary 用于兜底机制
|
|
250
|
+
this.sendStreamSummary(sessionId)
|
|
247
251
|
break
|
|
248
252
|
}
|
|
249
253
|
|
|
@@ -322,6 +326,10 @@ export class EyeClawWebSocketClient {
|
|
|
322
326
|
private sendChunk(content: string, sessionId?: string) {
|
|
323
327
|
const timestamp = new Date().toISOString();
|
|
324
328
|
const sequence = this.chunkSequence++;
|
|
329
|
+
|
|
330
|
+
// 累积完整内容用于兜底
|
|
331
|
+
this.accumulatedContent += content;
|
|
332
|
+
|
|
325
333
|
this.api.logger.info(`[EyeClaw] [${timestamp}] Sending chunk #${sequence} to Rails: "${content}"`);
|
|
326
334
|
this.sendMessage('stream_chunk', {
|
|
327
335
|
content,
|
|
@@ -329,6 +337,41 @@ export class EyeClawWebSocketClient {
|
|
|
329
337
|
sequence, // 添加序号
|
|
330
338
|
})
|
|
331
339
|
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* 发送 stream_summary 用于兜底机制
|
|
343
|
+
* 告诉 Rails 完整内容是什么,以便检测丢包并补偿
|
|
344
|
+
*/
|
|
345
|
+
private sendStreamSummary(sessionId?: string) {
|
|
346
|
+
// 计算内容 hash
|
|
347
|
+
const contentHash = this.hashCode(this.accumulatedContent);
|
|
348
|
+
|
|
349
|
+
this.api.logger.info(`[EyeClaw] Sending stream_summary: chunks=${this.chunkSequence}, content_len=${this.accumulatedContent.length}, hash=${contentHash}`);
|
|
350
|
+
|
|
351
|
+
this.sendMessage('stream_summary', {
|
|
352
|
+
session_id: sessionId,
|
|
353
|
+
total_content: this.accumulatedContent,
|
|
354
|
+
total_chunks: this.chunkSequence,
|
|
355
|
+
content_hash: contentHash,
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
// 重置累积内容(为下一个会话做准备)
|
|
359
|
+
this.accumulatedContent = '';
|
|
360
|
+
this.chunkSequence = 0;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* 简单 hash 函数
|
|
365
|
+
*/
|
|
366
|
+
private hashCode(str: string): string {
|
|
367
|
+
let hash = 0;
|
|
368
|
+
for (let i = 0; i < str.length; i++) {
|
|
369
|
+
const char = str.charCodeAt(i);
|
|
370
|
+
hash = ((hash << 5) - hash) + char;
|
|
371
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
372
|
+
}
|
|
373
|
+
return hash.toString(16);
|
|
374
|
+
}
|
|
332
375
|
|
|
333
376
|
/**
|
|
334
377
|
* 发送消息到 Rails(带 channel identifier)
|
|
@@ -351,7 +394,7 @@ export class EyeClawWebSocketClient {
|
|
|
351
394
|
*/
|
|
352
395
|
private startPing() {
|
|
353
396
|
this.pingInterval = setInterval(() => {
|
|
354
|
-
// 调用 Rails BotChannel 的 ping
|
|
397
|
+
// 调用 Rails BotChannel 的 ping 方法(使用 ActionCable 标准协议)
|
|
355
398
|
const channelIdentifier = JSON.stringify({
|
|
356
399
|
channel: 'BotChannel',
|
|
357
400
|
bot_id: this.config.botId,
|
|
@@ -362,6 +405,7 @@ export class EyeClawWebSocketClient {
|
|
|
362
405
|
identifier: channelIdentifier,
|
|
363
406
|
data: JSON.stringify({
|
|
364
407
|
action: 'ping',
|
|
408
|
+
timestamp: new Date().toISOString(),
|
|
365
409
|
}),
|
|
366
410
|
})
|
|
367
411
|
}, 60000) // 60秒心跳一次
|