@eyeclaw/eyeclaw 2.3.8 → 2.3.11

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eyeclaw/eyeclaw",
3
- "version": "2.3.8",
3
+ "version": "2.3.11",
4
4
  "description": "EyeClaw plugin for OpenClaw - HTTP SSE streaming + WebSocket client",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -74,7 +74,7 @@ export function createHttpHandler(api: OpenClawPluginApi, getConfig: () => EyeCl
74
74
 
75
75
  try {
76
76
  const body = await readJsonBody(req)
77
- const { message, session_id, stream_id } = body
77
+ const { message, session_id, stream_id, openclaw_session_id } = body
78
78
 
79
79
  if (!message) {
80
80
  res.statusCode = 400
@@ -84,6 +84,7 @@ export function createHttpHandler(api: OpenClawPluginApi, getConfig: () => EyeCl
84
84
  }
85
85
 
86
86
  logger.info(`[EyeClaw] Chat: ${message.substring(0, 50)}...`)
87
+ logger.info(`[EyeClaw] OpenClaw Session: ${openclaw_session_id || 'default'}`)
87
88
 
88
89
  // SSE 响应头
89
90
  res.setHeader('Content-Type', 'text/event-stream')
@@ -94,7 +95,8 @@ export function createHttpHandler(api: OpenClawPluginApi, getConfig: () => EyeCl
94
95
  // 获取 Gateway 配置
95
96
  const gatewayPort = api.config?.gateway?.port ?? 18789
96
97
  const gatewayToken = api.config?.gateway?.auth?.token
97
- const sessionKey = session_id ? `eyeclaw:${session_id}` : 'eyeclaw:default'
98
+ // 使用 openclaw_session_id 作为 OpenClaw 的会话标识,如果未提供则使用默认值
99
+ const sessionKey = openclaw_session_id || 'eyeclaw:default'
98
100
 
99
101
  // 调用 OpenClaw
100
102
  const openclawUrl = `http://127.0.0.1:${gatewayPort}/v1/chat/completions`
@@ -26,6 +26,7 @@ export class EyeClawWebSocketClient {
26
26
  private reconnectDelay = 3000
27
27
  private subscribed = false
28
28
  private pingInterval: any = null
29
+ private chunkSequence = 0 // 每个会话的 chunk 序号
29
30
 
30
31
  constructor(api: OpenClawPluginApi, config: EyeClawConfig, getState: () => any) {
31
32
  this.api = api
@@ -103,9 +104,9 @@ export class EyeClawWebSocketClient {
103
104
  return
104
105
  }
105
106
 
106
- // Ping/pong (协议级别的 ping,直接响应 pong)
107
+ // Ping/pong (WebSocket 协议级别的 ping 由浏览器自动响应,无需手动处理)
107
108
  if (message.type === 'ping') {
108
- this.send({ type: 'pong' })
109
+ this.api.logger.debug('[EyeClaw] Received protocol-level ping (auto-handled by WebSocket)')
109
110
  return
110
111
  }
111
112
 
@@ -175,21 +176,29 @@ export class EyeClawWebSocketClient {
175
176
  return
176
177
  }
177
178
 
178
- // 从 metadata 提取 session_id
179
+ // 从 metadata 提取 session_id (用于 Rails 内部追踪)
179
180
  const sessionId = metadata?.session_id
180
181
 
182
+ // 从 metadata 提取 openclaw_session_id (用于 OpenClaw 对话上下文)
183
+ // 如果未指定,使用 bot_id 作为默认值,这样同一个 Bot 的所有请求共享上下文
184
+ const openclawSessionId = metadata?.openclaw_session_id || `bot_${this.config.botId}`
185
+
181
186
  this.api.logger.info(`[EyeClaw] Processing: ${userMessage.substring(0, 50)}...`)
182
- this.api.logger.info(`[EyeClaw] Session ID: ${sessionId}`)
187
+ this.api.logger.info(`[EyeClaw] Rails Session ID: ${sessionId}`)
188
+ this.api.logger.info(`[EyeClaw] OpenClaw Session ID: ${openclawSessionId}`)
183
189
 
184
190
  // 通过 OpenClaw API 处理消息,获取流式响应
185
- await this.processWithOpenClaw(userMessage, sessionId)
191
+ await this.processWithOpenClaw(userMessage, sessionId, openclawSessionId)
186
192
  }
187
193
 
188
194
  /**
189
195
  * 使用 OpenClaw API 处理消息(流式)
190
196
  * 调用自己的 HTTP 端点 /eyeclaw/chat
191
197
  */
192
- private async processWithOpenClaw(message: string, sessionId?: string) {
198
+ private async processWithOpenClaw(message: string, sessionId?: string, openclawSessionId?: string) {
199
+ // 重置 chunk 序号(每个新会话)
200
+ this.chunkSequence = 0
201
+
193
202
  const state = this.getState()
194
203
  const gatewayPort = state.gatewayPort
195
204
  const eyeclawUrl = `http://127.0.0.1:${gatewayPort}/eyeclaw/chat`
@@ -197,6 +206,7 @@ export class EyeClawWebSocketClient {
197
206
  const requestBody = {
198
207
  message,
199
208
  session_id: sessionId,
209
+ openclaw_session_id: openclawSessionId,
200
210
  }
201
211
 
202
212
  const headers: Record<string, string> = {
@@ -311,10 +321,12 @@ export class EyeClawWebSocketClient {
311
321
  */
312
322
  private sendChunk(content: string, sessionId?: string) {
313
323
  const timestamp = new Date().toISOString();
314
- this.api.logger.info(`[EyeClaw] [${timestamp}] Sending chunk to Rails: "${content}"`);
324
+ const sequence = this.chunkSequence++;
325
+ this.api.logger.info(`[EyeClaw] [${timestamp}] Sending chunk #${sequence} to Rails: "${content}"`);
315
326
  this.sendMessage('stream_chunk', {
316
327
  content,
317
328
  session_id: sessionId,
329
+ sequence, // 添加序号
318
330
  })
319
331
  }
320
332
 
@@ -339,7 +351,7 @@ export class EyeClawWebSocketClient {
339
351
  */
340
352
  private startPing() {
341
353
  this.pingInterval = setInterval(() => {
342
- // 调用 Rails BotChannel 的 ping action
354
+ // 调用 Rails BotChannel 的 ping 方法(使用 ActionCable 标准协议)
343
355
  const channelIdentifier = JSON.stringify({
344
356
  channel: 'BotChannel',
345
357
  bot_id: this.config.botId,
@@ -350,6 +362,7 @@ export class EyeClawWebSocketClient {
350
362
  identifier: channelIdentifier,
351
363
  data: JSON.stringify({
352
364
  action: 'ping',
365
+ timestamp: new Date().toISOString(),
353
366
  }),
354
367
  })
355
368
  }, 60000) // 60秒心跳一次