@dingtalk-real-ai/dingtalk-connector 0.7.8 → 0.7.9

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/CHANGELOG.md CHANGED
@@ -6,6 +6,22 @@
6
6
  This document records all significant changes. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
7
7
  and version numbers follow [Semantic Versioning](https://semver.org/).
8
8
 
9
+ ## [0.7.9] - 2026-03-13
10
+
11
+ ### 新增 / Added
12
+ - ✨ **应用层心跳机制** - 钉钉 Stream 客户端使用自定义心跳(WebSocket ping/pong,30 秒间隔、90 秒超时),超时后主动断开并重连,重连失败 5 秒后重试
13
+ **Application-layer heartbeat** - Stream client uses custom ping/pong heartbeat (30s interval, 90s timeout), reconnects on timeout with 5s retry on failure
14
+ - ✨ **统一停止与清理** - 停止客户端时通过 `doStop` 统一清理心跳定时器并调用 `client.disconnect()`,确保连接正确关闭
15
+ **Unified stop & cleanup** - `doStop` clears heartbeat timer and calls `client.disconnect()` when stopping the client
16
+
17
+ ### 修复 / Fixes
18
+ - 🐛 **长连接静默断开** - 关闭 SDK 激进 keepAlive(8 秒超时),改用应用层心跳,减少因长时间无数据导致的静默断连且无法恢复
19
+ **Long-lived connection silent disconnect** - Disabled SDK aggressive keepAlive (8s timeout), use app-layer heartbeat to reduce silent disconnects when idle
20
+
21
+ ### 改进 / Improvements
22
+ - ✅ **DWClient 配置** - 启用 `autoReconnect: true`,设置 `keepAlive: false`,由应用层心跳替代 SDK 心跳,避免与钉钉服务端策略冲突
23
+ **DWClient config** - `autoReconnect: true`, `keepAlive: false`; app-layer heartbeat replaces SDK keepAlive to avoid conflicts with server
24
+
9
25
  ## [0.7.8] - 2026-03-13
10
26
 
11
27
  ### 修复 / Fixes
@@ -0,0 +1,65 @@
1
+ # Release Notes - v0.7.9
2
+
3
+ ## ✨ 功能与体验改进 / Features & Improvements
4
+
5
+ - **钉钉 Stream 客户端心跳与重连机制优化 / DingTalk Stream Client Heartbeat & Reconnect**
6
+ 关闭 DWClient SDK 内置的激进 keepAlive(避免 8 秒超时强制断连),启用应用层自定义心跳:基于 WebSocket ping/pong,30 秒间隔、90 秒超时,超时后主动断开并重连,重连失败时 5 秒后重试,提升长连稳定性。
7
+ Disabled the SDK's aggressive keepAlive (which could force disconnect after 8s), and added an application-layer heartbeat: WebSocket ping/pong with 30s interval and 90s timeout; on timeout the client disconnects and reconnects, with a 5s retry on failure, improving long-lived connection stability.
8
+
9
+ - **DWClient 配置调整 / DWClient Configuration**
10
+ 启用 `autoReconnect: true` 以在连接断开时自动重连;设置 `keepAlive: false`,由应用层心跳替代 SDK 心跳,避免与钉钉服务端策略冲突。
11
+ Enabled `autoReconnect: true` for automatic reconnection on disconnect; set `keepAlive: false` and rely on application-layer heartbeat to avoid conflicts with DingTalk server behavior.
12
+
13
+ - **统一停止与清理逻辑 / Unified Stop & Cleanup**
14
+ 停止 Stream 客户端时统一通过 `doStop` 清理心跳定时器并调用 `client.disconnect()`,确保资源释放与连接正确关闭。
15
+ When stopping the Stream client, a unified `doStop` now clears the heartbeat timer and calls `client.disconnect()` for consistent resource cleanup and connection closure.
16
+
17
+ ## 🐛 修复 / Fixes
18
+
19
+ - **长连接被服务端或中间网络提前断开 / Long-lived Connection Premature Disconnect**
20
+ 通过应用层心跳检测连接活性,超时后主动重连,减少因长时间无数据导致的静默断连且无法恢复的问题。
21
+ Application-layer heartbeat detects connection liveness and triggers reconnect on timeout, reducing silent disconnects when the link is idle.
22
+
23
+ ## 📋 技术细节 / Technical Details
24
+
25
+ ### 应用层心跳机制 / Application-Layer Heartbeat
26
+
27
+ - **参数**:心跳间隔 30 秒(`HEARTBEAT_INTERVAL`),超时 90 秒(`HEARTBEAT_TIMEOUT`),允许约 3 次 ping 无响应后再判定超时。
28
+ - **流程**:定时器每 30 秒通过 `client.socket?.ping()` 发送 ping;监听 `socket.on('pong')` 更新 `lastPongTime`;若当前时间与 `lastPongTime` 差值超过 90 秒则触发重连。
29
+ - **重连**:先 `await client.disconnect()`,再 `await client.connect()`,成功后重置 `lastPongTime`;若重连失败则 5 秒后再次尝试 `client.connect()`。
30
+ - **停止**:`doStop(reason)` 中设置 `stopped = true`、清除心跳定时器、调用 `client.disconnect()`,并记录停止原因与活动。
31
+
32
+ ### DWClient 配置说明 / DWClient Config
33
+
34
+ - `autoReconnect: true` — 连接断开时由 SDK 参与自动重连。
35
+ - `keepAlive: false` — 关闭 SDK 内置的激进心跳,避免 8 秒无活动即强制断连,由应用层 30s/90s 心跳替代。
36
+
37
+ ## 📥 安装升级 / Installation & Upgrade
38
+
39
+ ```bash
40
+ # 通过 npm 安装最新版本 / Install latest version via npm
41
+ openclaw plugins install @dingtalk-real-ai/dingtalk-connector
42
+
43
+ # 或升级现有版本 / Or upgrade existing version
44
+ openclaw plugins update dingtalk-connector
45
+
46
+ # 通过 Git 安装 / Install via Git
47
+ openclaw plugins install https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector.git
48
+ ```
49
+
50
+ ## ⚠️ 升级注意事项 / Upgrade Notes
51
+
52
+ - **向下兼容 / Backward Compatible**:仅调整 Stream 客户端的心跳与重连策略,对现有配置与 API 无破坏性变更。
53
+ - **长连场景建议**:若依赖钉钉 Stream 长连接,升级后将自动使用新的心跳与重连逻辑,无需额外配置。
54
+
55
+ ## 🔗 相关链接 / Related Links
56
+
57
+ - [完整变更日志 / Full Changelog](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/blob/main/CHANGELOG.md)
58
+ - [使用文档 / Documentation](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/blob/main/README.md)
59
+ - [问题反馈 / Issue Feedback](https://github.com/DingTalk-Real-AI/dingtalk-openclaw-connector/issues)
60
+
61
+ ---
62
+
63
+ **发布日期 / Release Date**:2026-03-13
64
+ **版本号 / Version**:v0.7.9
65
+ **兼容性 / Compatibility**:OpenClaw Gateway 0.4.0+
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "dingtalk-connector",
3
3
  "name": "DingTalk Channel",
4
- "version": "0.7.8",
4
+ "version": "0.7.9",
5
5
  "description": "DingTalk (钉钉) messaging channel via Stream mode with AI Card streaming",
6
6
  "author": "DingTalk Real Team",
7
7
  "channels": ["dingtalk-connector"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dingtalk-real-ai/dingtalk-connector",
3
- "version": "0.7.8",
3
+ "version": "0.7.9",
4
4
  "description": "DingTalk (钉钉) channel connector — Stream mode with AI Card streaming",
5
5
  "main": "plugin.ts",
6
6
  "type": "module",
package/plugin.ts CHANGED
@@ -3551,15 +3551,15 @@ const dingtalkPlugin = {
3551
3551
 
3552
3552
  ctx.log?.info(`[${account.accountId}] 启动钉钉 Stream 客户端...`);
3553
3553
 
3554
- // 启用 DWClient 内置的 autoReconnect 和 keepAlive
3554
+ // 配置 DWClient:关闭 SDK 内置的 keepAlive,使用应用层自定义心跳
3555
3555
  // - autoReconnect: 连接断开时自动重连
3556
- // - keepAlive: 启用心跳机制,防止服务端因长时间无活动而断开连接
3556
+ // - keepAlive: false(关闭 SDK 的激进心跳检测,避免 8 秒超时强制终止连接)
3557
3557
  const client = new DWClient({
3558
3558
  clientId: config.clientId,
3559
3559
  clientSecret: config.clientSecret,
3560
3560
  debug: config.debug || false,
3561
3561
  autoReconnect: true,
3562
- keepAlive: true,
3562
+ keepAlive: false,
3563
3563
  } as any);
3564
3564
 
3565
3565
  client.registerCallbackListener(TOPIC_ROBOT, async (res: any) => {
@@ -3611,16 +3611,110 @@ const dingtalkPlugin = {
3611
3611
 
3612
3612
  let stopped = false;
3613
3613
 
3614
+ // 【应用层心跳机制】基于 WebSocket ping/pong 的主动心跳检测
3615
+ // - 心跳间隔:30 秒
3616
+ // - 超时时间:90 秒(允许 3 次 ping 无响应)
3617
+ // - 检测方式:主动发送 ping,等待 pong 响应
3618
+ let lastPongTime = Date.now();
3619
+ let pendingPingId: string | null = null;
3620
+ const HEARTBEAT_INTERVAL = 30 * 1000; // 30 秒
3621
+ const HEARTBEAT_TIMEOUT = 90 * 1000; // 90 秒
3622
+
3623
+ // 监听 pong 响应(SDK 的 keepAlive=false 时仍然会收到服务端的 pong)
3624
+ client.socket?.on('pong', () => {
3625
+ lastPongTime = Date.now();
3626
+ pendingPingId = null;
3627
+ ctx.log?.debug?.(`[${account.accountId}] 收到 PONG 响应`);
3628
+ });
3629
+
3630
+ // 启动心跳检测定时器
3631
+ const heartbeatTimer = setInterval(async () => {
3632
+ if (stopped) {
3633
+ clearInterval(heartbeatTimer);
3634
+ return;
3635
+ }
3636
+
3637
+ const elapsed = Date.now() - lastPongTime;
3638
+
3639
+ // 如果超过 90 秒没有收到 pong,认为连接已断开
3640
+ if (elapsed > HEARTBEAT_TIMEOUT) {
3641
+ ctx.log?.warn?.(`[${account.accountId}] ⚠️ 心跳超时:已 ${Math.round(elapsed / 1000)} 秒未收到 PONG,触发重连...`);
3642
+
3643
+ // 【关键修复】主动重连:先断开再重新建立连接
3644
+ try {
3645
+ // 1. 先断开旧连接
3646
+ await client.disconnect();
3647
+ ctx.log?.info?.(`[${account.accountId}] 已断开旧连接`);
3648
+
3649
+ // 2. 重新建立连接
3650
+ ctx.log?.info?.(`[${account.accountId}] 正在重新建立连接...`);
3651
+ await client.connect();
3652
+
3653
+ // 3. 重置最后 pong 时间,避免立即再次触发重连
3654
+ lastPongTime = Date.now();
3655
+ pendingPingId = null;
3656
+
3657
+ ctx.log?.info?.(`[${account.accountId}] ✅ 重连成功`);
3658
+ } catch (err: any) {
3659
+ ctx.log?.error?.(`[${account.accountId}] ❌ 重连失败:${err.message}`);
3660
+ // 重连失败后,等待 5 秒后再次尝试
3661
+ ctx.log?.info?.(`[${account.accountId}] 5 秒后再次尝试重连...`);
3662
+ setTimeout(async () => {
3663
+ try {
3664
+ await client.connect();
3665
+ lastPongTime = Date.now();
3666
+ pendingPingId = null;
3667
+ ctx.log?.info?.(`[${account.accountId}] ✅ 重试重连成功`);
3668
+ } catch (retryErr: any) {
3669
+ ctx.log?.error?.(`[${account.accountId}] ❌ 重试重连失败:${retryErr.message}`);
3670
+ }
3671
+ }, 5000);
3672
+ }
3673
+ return;
3674
+ }
3675
+
3676
+ // 如果还有 ping 在等待响应,检查是否超时
3677
+ if (pendingPingId) {
3678
+ ctx.log?.debug?.(`[${account.accountId}] 心跳检测:等待 PONG 响应中...`);
3679
+ return;
3680
+ }
3681
+
3682
+ // 主动发送 ping 消息
3683
+ try {
3684
+ const pingId = `ping_${Date.now()}`;
3685
+ pendingPingId = pingId;
3686
+
3687
+ // 通过 WebSocket 直接发送 ping(使用 SDK 的 socket)
3688
+ client.socket?.ping(JSON.stringify({
3689
+ type: 'PING',
3690
+ id: pingId,
3691
+ timestamp: Date.now()
3692
+ }));
3693
+
3694
+ ctx.log?.debug?.(`[${account.accountId}] 发送 PING 请求:${pingId}`);
3695
+ } catch (err: any) {
3696
+ ctx.log?.error?.(`[${account.accountId}] 发送 PING 失败:${err.message}`);
3697
+ // 发送失败也计入超时
3698
+ }
3699
+ }, HEARTBEAT_INTERVAL);
3700
+
3614
3701
  // 统一的停止逻辑
3615
3702
  const doStop = (reason: string) => {
3616
3703
  if (stopped) return;
3617
3704
  stopped = true;
3618
3705
  ctx.log?.info(`[${account.accountId}] 停止钉钉 Stream 客户端 (${reason})...`);
3706
+
3707
+ // 清理心跳定时器
3708
+ if (typeof heartbeatTimer !== 'undefined') {
3709
+ clearInterval(heartbeatTimer);
3710
+ ctx.log?.debug?.(`[${account.accountId}] 心跳定时器已清理`);
3711
+ }
3712
+
3619
3713
  try {
3620
3714
  // 【关键】调用 disconnect() 正确关闭 WebSocket 连接
3621
3715
  client.disconnect();
3622
3716
  } catch (err: any) {
3623
- ctx.log?.warn?.(`[${account.accountId}] 断开连接时出错: ${err.message}`);
3717
+ ctx.log?.warn?.(`[${account.accountId}] 断开连接时出错:${err.message}`);
3624
3718
  }
3625
3719
  rt.channel.activity.record('dingtalk-connector', account.accountId, 'stop');
3626
3720
  };