@eyeclaw/eyeclaw 2.4.3 → 2.4.4

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.4.3",
3
+ "version": "2.4.4",
4
4
  "description": "EyeClaw plugin for OpenClaw - HTTP SSE streaming + WebSocket client",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -123,12 +123,19 @@ export class EyeClawWebSocketClient {
123
123
  this.stopPing()
124
124
  this.lastCloseCode = closeCode
125
125
 
126
+ // 保存之前的连接状态用于检测部署
127
+ const previouslyConnected = this.wasConnected
128
+ const lastConnected = this.lastConnectedAt
129
+
130
+ // 标记为未连接状态,防止死循环
131
+ this.wasConnected = false
132
+
126
133
  // 统计连续失败
127
134
  this.consecutiveFailures++
128
135
 
129
136
  // 检测是否是部署导致的断连(之前已成功连接过,且断开时间 > 5 秒)
130
137
  // 或者关闭码为 1000/1001(正常关闭)但之前已连接
131
- if (this.wasConnected && Date.now() - this.lastConnectedAt > 5000) {
138
+ if (previouslyConnected && Date.now() - lastConnected > 5000) {
132
139
  // 正常关闭或部署重启
133
140
  if (wasClean) {
134
141
  this.deploymentDetected = true
@@ -138,7 +145,7 @@ export class EyeClawWebSocketClient {
138
145
  this.deploymentDetected = true
139
146
  this.api.logger.info('[EyeClaw] 🔄 Server restart detected (unclean close), will reconnect immediately')
140
147
  }
141
- } else if (!this.wasConnected && !wasClean) {
148
+ } else if (!previouslyConnected && !wasClean) {
142
149
  // 首次连接失败,增加重试延迟
143
150
  this.api.logger.warn('[EyeClaw] Initial connection failed, increasing delay...')
144
151
  }
@@ -182,8 +189,13 @@ export class EyeClawWebSocketClient {
182
189
  * 检测网络状态并在需要时触发重连
183
190
  */
184
191
  private async checkNetworkAndReconnectIfNeeded() {
185
- // 如果已经在重连中或者 WebSocket 已经连接,跳过
186
- if (this.reconnecting || (this.ws && this.ws.readyState === WebSocket.OPEN)) {
192
+ // 如果 WebSocket 已经连接,跳过(不需要重连)
193
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
194
+ return
195
+ }
196
+
197
+ // 如果正在重连中,跳过
198
+ if (this.reconnecting) {
187
199
  return
188
200
  }
189
201
 
@@ -205,7 +217,7 @@ export class EyeClawWebSocketClient {
205
217
 
206
218
  if (!wasOnline && this.networkOnline) {
207
219
  // 网络从离线变为在线,触发重连
208
- this.api.logger.info('[EyeClaw] 🌐 Network restored, triggering immediate reconnect')
220
+ this.api.logger.info('[EyeClaw] 🌐 Network restored, triggering reconnect')
209
221
  this.deploymentDetected = true // 使用快速重连模式
210
222
  this.scheduleReconnect()
211
223
  } else if (this.networkOnline && !this.wasConnected) {
@@ -785,14 +797,22 @@ export class EyeClawWebSocketClient {
785
797
  }
786
798
 
787
799
  // 🚀 部署恢复场景或网络恢复:立即重连(无延迟)
800
+ // 但需要限制次数,防止无限立即重连失败导致死循环
788
801
  if (this.deploymentDetected) {
789
- this.api.logger.info('[EyeClaw] Deployment recovery or network restored: immediate reconnect')
790
- this.reconnectTimer = setTimeout(() => {
791
- this.reconnecting = false
792
- this.start()
793
- }, 100) // 100ms 延迟(给服务器一点启动时间)
794
- this.deploymentDetected = false // 重置标志
795
- return
802
+ // 如果连续 3 次立即重连都失败,说明可能是网络问题,不再使用立即重连
803
+ if (this.consecutiveFailures > 3) {
804
+ this.api.logger.warn('[EyeClaw] ⚠️ Too many immediate reconnects failed, switching to backoff mode')
805
+ this.deploymentDetected = false
806
+ // 继续使用正常重连逻辑
807
+ } else {
808
+ this.api.logger.info('[EyeClaw] ⚡ Deployment recovery or network restored: immediate reconnect')
809
+ this.reconnectTimer = setTimeout(() => {
810
+ this.reconnecting = false
811
+ this.start()
812
+ }, 100) // 100ms 延迟(给服务器一点启动时间)
813
+ this.deploymentDetected = false // 重置标志
814
+ return
815
+ }
796
816
  }
797
817
 
798
818
  // 检测是否是网络断开导致的失败(code 0)
@@ -824,9 +844,15 @@ export class EyeClawWebSocketClient {
824
844
  * 健康检查后重连
825
845
  */
826
846
  private async performHealthCheckAndReconnect() {
847
+ // 如果 WebSocket 已经连接,不需要重连
848
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
849
+ this.api.logger?.debug?.('[EyeClaw] ✅ WebSocket already connected, skipping reconnect')
850
+ return
851
+ }
852
+
827
853
  try {
828
854
  // 尝试 HTTP 健康检查
829
- const serverUrl = this.config.serverUrl.replace(/^http/, 'http') // 使用 http 而非 ws
855
+ const serverUrl = this.config.serverUrl.replace(/^ws/, 'http').replace(/^wss/, 'https')
830
856
  const healthUrl = `${serverUrl}/api/v1/health`
831
857
 
832
858
  const controller = new AbortController()
@@ -840,7 +866,7 @@ export class EyeClawWebSocketClient {
840
866
  clearTimeout(timeoutId)
841
867
 
842
868
  if (response.ok) {
843
- this.api.logger.info('[EyeClaw] ✅ Health check passed, proceeding with reconnect')
869
+ this.api.logger.info('[EyeClaw] ✅ Health check passed, connecting...')
844
870
  this.start()
845
871
  } else {
846
872
  this.api.logger.warn(`[EyeClaw] ⚠️ Health check failed (${response.status}), retrying soon...`)
@@ -851,9 +877,12 @@ export class EyeClawWebSocketClient {
851
877
  }, 2000)
852
878
  }
853
879
  } catch (error) {
854
- this.api.logger.warn(`[EyeClaw] ⚠️ Health check error: ${error}, proceeding with reconnect anyway`)
855
- // 即使健康检查失败也尝试重连
856
- this.start()
880
+ this.api.logger.warn(`[EyeClaw] ⚠️ Health check error: ${error}, retrying soon...`)
881
+ // 健康检查失败,稍后重试
882
+ this.reconnectTimer = setTimeout(() => {
883
+ this.reconnecting = false
884
+ this.scheduleReconnect()
885
+ }, 2000)
857
886
  }
858
887
  }
859
888