@coclaw/openclaw-coclaw 0.9.1 → 0.9.2

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": "@coclaw/openclaw-coclaw",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "OpenClaw CoClaw channel plugin for remote chat",
@@ -98,6 +98,7 @@ export class RealtimeBridge {
98
98
  this.__serverHbMissCount = 0;
99
99
  this.__deviceIdentity = null;
100
100
  this.webrtcPeer = null;
101
+ this.__webrtcPeerReady = null;
101
102
  this.__fileHandler = null;
102
103
  }
103
104
 
@@ -196,6 +197,35 @@ export class RealtimeBridge {
196
197
  this.gatewayPendingRequests.clear();
197
198
  }
198
199
 
200
+ /** 懒加载 WebRtcPeer(promise 锁防并发重复创建) */
201
+ async __initWebrtcPeer() {
202
+ const { WebRtcPeer } = await import('./webrtc-peer.js');
203
+ const { createFileHandler } = await import('./file-manager/handler.js');
204
+ /* c8 ignore start -- 仅通过 WebRTC 路径触发,集成测试覆盖 */
205
+ this.__fileHandler = createFileHandler({
206
+ resolveWorkspace: (agentId) => this.__resolveWorkspace(agentId),
207
+ logger: this.logger,
208
+ });
209
+ this.__fileHandler.scheduleTmpCleanup(() => this.__listAgentWorkspaces());
210
+ /* c8 ignore stop */
211
+ this.webrtcPeer = new WebRtcPeer({
212
+ onSend: (msg) => this.__forwardToServer(msg),
213
+ onRequest: (dcPayload) => {
214
+ void this.__handleGatewayRequestFromServer(dcPayload);
215
+ },
216
+ /* c8 ignore start -- 仅通过 WebRTC 路径触发,集成测试覆盖 */
217
+ onFileRpc: (payload, sendFn) => {
218
+ this.__fileHandler.handleRpcRequest(payload, sendFn)
219
+ .catch((err) => this.logger.warn?.(`[coclaw/file] rpc error: ${err.message}`));
220
+ },
221
+ onFileChannel: (dc) => {
222
+ this.__fileHandler.handleFileChannel(dc);
223
+ },
224
+ /* c8 ignore stop */
225
+ logger: this.logger,
226
+ });
227
+ }
228
+
199
229
  /* c8 ignore next 7 -- 防御性检查,serverWs 通常在调用时可用 */
200
230
  __forwardToServer(payload) {
201
231
  if (!this.serverWs || this.serverWs.readyState !== 1) {
@@ -681,9 +711,12 @@ export class RealtimeBridge {
681
711
  if (!this.started || this.reconnectTimer) {
682
712
  return;
683
713
  }
684
- this.reconnectTimer = setTimeout(async () => {
714
+ this.reconnectTimer = setTimeout(() => {
685
715
  this.reconnectTimer = null;
686
- await this.__connectIfNeeded();
716
+ this.__connectIfNeeded().catch((err) => {
717
+ /* c8 ignore next -- 防御性兜底,__connectIfNeeded 内部已有完整错误处理 */
718
+ this.logger.warn?.(`[coclaw] reconnect failed: ${err?.message}`);
719
+ });
687
720
  }, RECONNECT_MS);
688
721
  this.reconnectTimer.unref?.();
689
722
  }
@@ -754,33 +787,13 @@ export class RealtimeBridge {
754
787
  }
755
788
  if (payload?.type?.startsWith('rtc:')) {
756
789
  try {
757
- if (!this.webrtcPeer) {
758
- const { WebRtcPeer } = await import('./webrtc-peer.js');
759
- const { createFileHandler } = await import('./file-manager/handler.js');
760
- /* c8 ignore start -- 仅通过 WebRTC 路径触发,集成测试覆盖 */
761
- this.__fileHandler = createFileHandler({
762
- resolveWorkspace: (agentId) => this.__resolveWorkspace(agentId),
763
- logger: this.logger,
764
- });
765
- this.__fileHandler.scheduleTmpCleanup(() => this.__listAgentWorkspaces());
766
- /* c8 ignore stop */
767
- this.webrtcPeer = new WebRtcPeer({
768
- onSend: (msg) => this.__forwardToServer(msg),
769
- onRequest: (dcPayload) => {
770
- void this.__handleGatewayRequestFromServer(dcPayload);
771
- },
772
- /* c8 ignore start -- 仅通过 WebRTC 路径触发,集成测试覆盖 */
773
- onFileRpc: (payload, sendFn) => {
774
- this.__fileHandler.handleRpcRequest(payload, sendFn)
775
- .catch((err) => this.logger.warn?.(`[coclaw/file] rpc error: ${err.message}`));
776
- },
777
- onFileChannel: (dc) => {
778
- this.__fileHandler.handleFileChannel(dc);
779
- },
780
- /* c8 ignore stop */
781
- logger: this.logger,
790
+ if (!this.__webrtcPeerReady) {
791
+ this.__webrtcPeerReady = this.__initWebrtcPeer().catch((err) => {
792
+ this.__webrtcPeerReady = null;
793
+ throw err;
782
794
  });
783
795
  }
796
+ await this.__webrtcPeerReady;
784
797
  await this.webrtcPeer.handleSignaling(payload);
785
798
  } catch (err) {
786
799
  this.logger.warn?.(`[coclaw/rtc] signaling error (or werift not found): ${err?.message}`);
@@ -816,6 +829,7 @@ export class RealtimeBridge {
816
829
  /* c8 ignore next 3 -- 防御性兜底,werift close 异常时不可崩溃 gateway */
817
830
  catch (e) { this.logger.warn?.(`[coclaw/rtc] closeAll failed: ${e?.message}`); }
818
831
  this.webrtcPeer = null;
832
+ this.__webrtcPeerReady = null;
819
833
  }
820
834
  if (this.__fileHandler) {
821
835
  this.__fileHandler.cancelCleanup();
@@ -882,6 +896,7 @@ export class RealtimeBridge {
882
896
  if (this.webrtcPeer) {
883
897
  await this.webrtcPeer.closeAll().catch(() => {});
884
898
  this.webrtcPeer = null;
899
+ this.__webrtcPeerReady = null;
885
900
  }
886
901
  if (this.__fileHandler) {
887
902
  this.__fileHandler.cancelCleanup();