@eyeclaw/eyeclaw 2.4.3 → 2.4.5

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.5",
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,14 +189,20 @@ 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
 
190
202
  try {
191
203
  // 尝试一个简单的 HTTP 请求来检测网络是否可用
192
- const serverUrl = this.config.serverUrl.replace(/^ws/, 'http').replace(/^wss/, 'https')
204
+ // 直接使用配置的 serverUrl,不需要转换
205
+ const serverUrl = this.config.serverUrl
193
206
  const controller = new AbortController()
194
207
  const timeoutId = setTimeout(() => controller.abort(), 3000)
195
208
 
@@ -205,7 +218,7 @@ export class EyeClawWebSocketClient {
205
218
 
206
219
  if (!wasOnline && this.networkOnline) {
207
220
  // 网络从离线变为在线,触发重连
208
- this.api.logger.info('[EyeClaw] 🌐 Network restored, triggering immediate reconnect')
221
+ this.api.logger.info('[EyeClaw] 🌐 Network restored, triggering reconnect')
209
222
  this.deploymentDetected = true // 使用快速重连模式
210
223
  this.scheduleReconnect()
211
224
  } else if (this.networkOnline && !this.wasConnected) {
@@ -785,14 +798,22 @@ export class EyeClawWebSocketClient {
785
798
  }
786
799
 
787
800
  // 🚀 部署恢复场景或网络恢复:立即重连(无延迟)
801
+ // 但需要限制次数,防止无限立即重连失败导致死循环
788
802
  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
803
+ // 如果连续 3 次立即重连都失败,说明可能是网络问题,不再使用立即重连
804
+ if (this.consecutiveFailures > 3) {
805
+ this.api.logger.warn('[EyeClaw] ⚠️ Too many immediate reconnects failed, switching to backoff mode')
806
+ this.deploymentDetected = false
807
+ // 继续使用正常重连逻辑
808
+ } else {
809
+ this.api.logger.info('[EyeClaw] ⚡ Deployment recovery or network restored: immediate reconnect')
810
+ this.reconnectTimer = setTimeout(() => {
811
+ this.reconnecting = false
812
+ this.start()
813
+ }, 100) // 100ms 延迟(给服务器一点启动时间)
814
+ this.deploymentDetected = false // 重置标志
815
+ return
816
+ }
796
817
  }
797
818
 
798
819
  // 检测是否是网络断开导致的失败(code 0)
@@ -824,9 +845,16 @@ export class EyeClawWebSocketClient {
824
845
  * 健康检查后重连
825
846
  */
826
847
  private async performHealthCheckAndReconnect() {
848
+ // 如果 WebSocket 已经连接,不需要重连
849
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
850
+ this.api.logger?.debug?.('[EyeClaw] ✅ WebSocket already connected, skipping reconnect')
851
+ return
852
+ }
853
+
827
854
  try {
828
855
  // 尝试 HTTP 健康检查
829
- const serverUrl = this.config.serverUrl.replace(/^http/, 'http') // 使用 http 而非 ws
856
+ // 直接使用配置的 serverUrl,不需要转换
857
+ const serverUrl = this.config.serverUrl
830
858
  const healthUrl = `${serverUrl}/api/v1/health`
831
859
 
832
860
  const controller = new AbortController()
@@ -840,7 +868,7 @@ export class EyeClawWebSocketClient {
840
868
  clearTimeout(timeoutId)
841
869
 
842
870
  if (response.ok) {
843
- this.api.logger.info('[EyeClaw] ✅ Health check passed, proceeding with reconnect')
871
+ this.api.logger.info('[EyeClaw] ✅ Health check passed, connecting...')
844
872
  this.start()
845
873
  } else {
846
874
  this.api.logger.warn(`[EyeClaw] ⚠️ Health check failed (${response.status}), retrying soon...`)
@@ -851,9 +879,12 @@ export class EyeClawWebSocketClient {
851
879
  }, 2000)
852
880
  }
853
881
  } catch (error) {
854
- this.api.logger.warn(`[EyeClaw] ⚠️ Health check error: ${error}, proceeding with reconnect anyway`)
855
- // 即使健康检查失败也尝试重连
856
- this.start()
882
+ this.api.logger.warn(`[EyeClaw] ⚠️ Health check error: ${error}, retrying soon...`)
883
+ // 健康检查失败,稍后重试
884
+ this.reconnectTimer = setTimeout(() => {
885
+ this.reconnecting = false
886
+ this.scheduleReconnect()
887
+ }, 2000)
857
888
  }
858
889
  }
859
890