@coclaw/openclaw-coclaw 0.11.2 → 0.11.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": "@coclaw/openclaw-coclaw",
3
- "version": "0.11.2",
3
+ "version": "0.11.4",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "OpenClaw CoClaw channel plugin for remote chat",
@@ -41,7 +41,7 @@ async function restartGatewayProcess(spawnFn) {
41
41
  }
42
42
  /* c8 ignore stop */
43
43
 
44
- // bind/unbind/enroll 的 RPC 超时(覆盖默认 10s)
44
+ // bind/unbind/enroll 的 RPC 超时(覆盖 openclaw gateway call 默认 10s)
45
45
  // 卡点是 gateway ↔ server 的网络通信,bind 最多两次(先解绑再绑定)
46
46
  const RPC_TIMEOUT_MS = 30_000;
47
47
 
@@ -27,6 +27,7 @@ export function escapeJsonForCmd(json) {
27
27
  * 2. 监听 stdout,解析 JSON 输出判断 RPC 成功/失败
28
28
  * 3. 检测到完整 JSON 后启动 KILL_DELAY_MS grace period 等待自然退出
29
29
  * 4. 总超时默认 NOTIFY_TIMEOUT_MS(注册 CLI 路径覆盖为 30s)
30
+ * 同时通过 `--timeout` 传递给子进程,确保内外层超时一致
30
31
  * 5. 无论成功失败,最终都主动 kill 子进程
31
32
  *
32
33
  * grace period 设计:openclaw 进程因 WS handle 滞留可能 10s+ 才退出,
@@ -55,6 +56,10 @@ export function callGatewayMethod(method, spawnFn, opts) {
55
56
  try {
56
57
  const isWin = opts?.isWin ?? IS_WIN;
57
58
  const args = ['gateway', 'call', method, '--json'];
59
+ // 将超时传递给 openclaw gateway call(默认 10s),避免内外层超时不一致
60
+ if (opts?.timeoutMs) {
61
+ args.push('--timeout', String(opts.timeoutMs));
62
+ }
58
63
  if (opts?.params) {
59
64
  const json = JSON.stringify(opts.params);
60
65
  // Windows 需 shell 解析 .cmd → 必须转义 JSON;非 Windows 不经 shell,直传
@@ -941,11 +941,9 @@ export class RealtimeBridge {
941
941
  .then((v) => { this.__pluginVersion = v; })
942
942
  .catch(() => { this.__pluginVersion = 'unknown'; }),
943
943
  ]);
944
- // 竞态保护:若 preload 期间 stop() 已执行,不再赋值,立即释放 cleanup
944
+ // 竞态保护:若 preload 期间 stop() 已执行,不再赋值,直接返回。
945
+ // 不调 cleanup()——与 stop() 策略一致,native threads 保持活跃供后续复用。
945
946
  if (!this.started) {
946
- if (preloadResult.cleanup) {
947
- try { preloadResult.cleanup(); } catch {}
948
- }
949
947
  return;
950
948
  }
951
949
  this.__ndcPreloadResult = preloadResult;
@@ -979,15 +977,12 @@ export class RealtimeBridge {
979
977
  this.webrtcPeer = null;
980
978
  this.__webrtcPeerReady = null;
981
979
  }
982
- // ndc cleanup:node-datachannel native threads 必须通过 cleanup() 释放,
983
- // 否则会阻止进程退出(issue #366)。
984
- // start() await preload 完成并缓存 cleanup 引用,此处直接使用。
985
- // 注意:若进程被 SIGKILL 强杀,此处不会执行,OS 会回收资源。
986
- // TODO: OpenClaw 未来提供 graceful shutdown 钩子,应在钩子中也调用 cleanup。
987
- if (this.__ndcCleanup) {
988
- try { this.__ndcCleanup(); }
989
- catch (err) { remoteLog(`ndc.cleanup-failed error=${err?.message}`); }
990
- }
980
+ // 不在 stop() 中调用 ndc.cleanup()
981
+ // cleanup() 是同步 native 调用,需 join native threads,耗时 10s+,
982
+ // 会阻塞事件循环导致 RPC handler 超时。
983
+ // gateway 是长驻进程,native threads 保持活跃即可;
984
+ // 下次 start() 重新 import(ESM 缓存命中)可直接复用。
985
+ // 进程退出时 OS 会回收所有资源。
991
986
  this.__ndcCleanup = null;
992
987
  this.__ndcPreloadResult = null;
993
988
  if (this.__fileHandler) {
@@ -60,6 +60,33 @@ export function defaultResolvePaths(platformKey, pluginRoot) {
60
60
  return { src, dest, destDir };
61
61
  }
62
62
 
63
+ /**
64
+ * ndc polyfill 的 RTCPeerConnection 将 iceServers 的 username:credential 直接拼入 URL,
65
+ * 但 TURN REST API 的 username 格式为 `timestamp:identity`(含冒号),
66
+ * 导致 libdatachannel 的 URL parser 截断 username。
67
+ * 此 wrapper 在传入 polyfill 前对 username/credential 做 percent-encoding 规避该问题。
68
+ */
69
+ function wrapNdcCredentials(NativeRTC) {
70
+ return class extends NativeRTC {
71
+ constructor(config = {}) {
72
+ if (config?.iceServers) {
73
+ config = {
74
+ ...config,
75
+ iceServers: config.iceServers.map(s => {
76
+ if (!s.username && !s.credential) return s;
77
+ return {
78
+ ...s,
79
+ username: s.username ? encodeURIComponent(s.username) : s.username,
80
+ credential: s.credential ? encodeURIComponent(s.credential) : s.credential,
81
+ };
82
+ }),
83
+ };
84
+ }
85
+ super(config);
86
+ }
87
+ };
88
+ }
89
+
63
90
  /**
64
91
  * 预加载 WebRTC 实现:优先 node-datachannel,失败回退 werift,全部失败返回 null。
65
92
  *
@@ -160,7 +187,7 @@ export async function preloadNdc(deps = {}) {
160
187
  // 当前由 RealtimeBridge.stop() 负责调用。若 gateway 被 SIGKILL 强杀则无法执行,
161
188
  // 但 OS 会回收所有资源。若 OpenClaw 提供了优雅终止钩子,应在钩子中也调用 cleanup。
162
189
  log(`ndc.loaded platform=${platformKey}`);
163
- return { PeerConnection: RTCPeerConnection, cleanup, impl: 'ndc' };
190
+ return { PeerConnection: wrapNdcCredentials(RTCPeerConnection), cleanup, impl: 'ndc' };
164
191
  } catch (err) {
165
192
  // resolvePaths 或其他未预期异常的兜底
166
193
  log(`ndc.fallback reason=unexpected error=${err.message}`);