@jeik/dingtalk-connector 0.8.21

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.
Files changed (154) hide show
  1. package/CHANGELOG.md +684 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +179 -0
  4. package/README.md +219 -0
  5. package/bin/dingtalk-connector.js +838 -0
  6. package/bin/wizard-config.mjs +94 -0
  7. package/dist/accounts-BAzdqkAV.mjs +268 -0
  8. package/dist/accounts-BQptOmgB.mjs +2 -0
  9. package/dist/chunk-upload-BBQgGtcZ.mjs +193 -0
  10. package/dist/chunk-upload-DaLXXZH3.mjs +2 -0
  11. package/dist/common-C8pYKU_y.mjs +2 -0
  12. package/dist/common-Dt9n6fQN.mjs +101 -0
  13. package/dist/connection-DHHFFNQJ.mjs +423 -0
  14. package/dist/entry-bundled.d.mts +16 -0
  15. package/dist/entry-bundled.mjs +31 -0
  16. package/dist/game-xiyou-CqHt-6Q1.mjs +4271 -0
  17. package/dist/gateway-methods-C4tcgI7P.mjs +771 -0
  18. package/dist/gateway-methods-Ci31A3vg.mjs +2 -0
  19. package/dist/http-client-CpnJHB89.mjs +2 -0
  20. package/dist/http-client-DFWZgO1n.mjs +33 -0
  21. package/dist/index.d.mts +193 -0
  22. package/dist/index.mjs +45 -0
  23. package/dist/logger-BmJkQkm1.mjs +2 -0
  24. package/dist/logger-mZ9OSbmD.mjs +58 -0
  25. package/dist/media-C_SVin7s.mjs +2 -0
  26. package/dist/media-cz72EVS3.mjs +509 -0
  27. package/dist/message-handler-DESzFFDc.mjs +1971 -0
  28. package/dist/messaging-B6l1sRvX.mjs +1044 -0
  29. package/dist/runtime-DUgpo5zC.mjs +1422 -0
  30. package/dist/session-DJ4jYqPv.mjs +114 -0
  31. package/dist/utils-Bjh4r_qS.mjs +4 -0
  32. package/dist/utils-CIfI_3Jh.mjs +63 -0
  33. package/dist/utils-legacy-CALCPP1t.mjs +230 -0
  34. package/dist/utils-legacy-CFYDBM4r.mjs +3 -0
  35. package/docs/DEAP_AGENT_GUIDE.en.md +115 -0
  36. package/docs/DEAP_AGENT_GUIDE.md +115 -0
  37. package/docs/DINGTALK_MANUAL_SETUP.md +50 -0
  38. package/docs/MULTI_AGENT_SETUP.md +306 -0
  39. package/docs/RELEASE_NOTES_V0.7.10.md +40 -0
  40. package/docs/RELEASE_NOTES_V0.7.2.md +143 -0
  41. package/docs/RELEASE_NOTES_V0.7.3.md +149 -0
  42. package/docs/RELEASE_NOTES_V0.7.4.md +206 -0
  43. package/docs/RELEASE_NOTES_V0.7.5.md +267 -0
  44. package/docs/RELEASE_NOTES_V0.7.6.md +219 -0
  45. package/docs/RELEASE_NOTES_V0.7.7.md +122 -0
  46. package/docs/RELEASE_NOTES_V0.7.8.md +101 -0
  47. package/docs/RELEASE_NOTES_V0.7.9.md +65 -0
  48. package/docs/RELEASE_NOTES_V0.8.0.md +53 -0
  49. package/docs/RELEASE_NOTES_V0.8.1.md +47 -0
  50. package/docs/RELEASE_NOTES_V0.8.10.md +49 -0
  51. package/docs/RELEASE_NOTES_V0.8.11.md +51 -0
  52. package/docs/RELEASE_NOTES_V0.8.12.md +63 -0
  53. package/docs/RELEASE_NOTES_V0.8.13-beta.0.md +69 -0
  54. package/docs/RELEASE_NOTES_V0.8.13.md +62 -0
  55. package/docs/RELEASE_NOTES_V0.8.14.md +86 -0
  56. package/docs/RELEASE_NOTES_V0.8.16.md +40 -0
  57. package/docs/RELEASE_NOTES_V0.8.17.md +87 -0
  58. package/docs/RELEASE_NOTES_V0.8.18.md +64 -0
  59. package/docs/RELEASE_NOTES_V0.8.19.md +62 -0
  60. package/docs/RELEASE_NOTES_V0.8.2.md +55 -0
  61. package/docs/RELEASE_NOTES_V0.8.20.md +49 -0
  62. package/docs/RELEASE_NOTES_V0.8.3.md +63 -0
  63. package/docs/RELEASE_NOTES_V0.8.4.md +45 -0
  64. package/docs/RELEASE_NOTES_V0.8.7.md +49 -0
  65. package/docs/RELEASE_NOTES_V0.8.8.md +63 -0
  66. package/docs/RELEASE_NOTES_V0.8.9.md +81 -0
  67. package/docs/RELEASE_NOTES_v0.7.0.md +142 -0
  68. package/docs/RELEASE_NOTES_v0.7.1.md +74 -0
  69. package/docs/TROUBLESHOOTING.md +122 -0
  70. package/index.ts +77 -0
  71. package/openclaw.plugin.json +551 -0
  72. package/package.json +147 -0
  73. package/skills/dingtalk-channel-rules/SKILL.md +91 -0
  74. package/skills/dingtalk-troubleshoot/SKILL.md +93 -0
  75. package/skills/dws-cli/SKILL.md +129 -0
  76. package/skills/dws-cli/references/error-codes.md +95 -0
  77. package/skills/dws-cli/references/field-rules.md +105 -0
  78. package/skills/dws-cli/references/global-reference.md +104 -0
  79. package/skills/dws-cli/references/intent-guide.md +114 -0
  80. package/skills/dws-cli/references/products/aitable.md +452 -0
  81. package/skills/dws-cli/references/products/attendance.md +93 -0
  82. package/skills/dws-cli/references/products/calendar.md +217 -0
  83. package/skills/dws-cli/references/products/chat.md +292 -0
  84. package/skills/dws-cli/references/products/contact.md +108 -0
  85. package/skills/dws-cli/references/products/ding.md +57 -0
  86. package/skills/dws-cli/references/products/report.md +162 -0
  87. package/skills/dws-cli/references/products/simple.md +128 -0
  88. package/skills/dws-cli/references/products/todo.md +138 -0
  89. package/skills/dws-cli/references/products/workbench.md +39 -0
  90. package/skills/dws-cli/references/recovery-guide.md +94 -0
  91. package/src/channel.ts +588 -0
  92. package/src/config/accounts.ts +242 -0
  93. package/src/config/schema.ts +180 -0
  94. package/src/core/connection.ts +741 -0
  95. package/src/core/message-handler.ts +1788 -0
  96. package/src/core/provider.ts +111 -0
  97. package/src/core/state.ts +54 -0
  98. package/src/device-auth-config.ts +14 -0
  99. package/src/device-auth.ts +197 -0
  100. package/src/directory.ts +95 -0
  101. package/src/docs.ts +293 -0
  102. package/src/game-xiyou/achievement-engine.ts +252 -0
  103. package/src/game-xiyou/bounty-system.ts +315 -0
  104. package/src/game-xiyou/commands.ts +223 -0
  105. package/src/game-xiyou/drop-engine.ts +241 -0
  106. package/src/game-xiyou/encounter-system.ts +135 -0
  107. package/src/game-xiyou/escape-engine.ts +164 -0
  108. package/src/game-xiyou/exp-calculator.ts +139 -0
  109. package/src/game-xiyou/index.ts +479 -0
  110. package/src/game-xiyou/level-system.ts +91 -0
  111. package/src/game-xiyou/monster-pool.ts +180 -0
  112. package/src/game-xiyou/pity-counter.ts +114 -0
  113. package/src/game-xiyou/random-event-engine.ts +648 -0
  114. package/src/game-xiyou/renderer.ts +679 -0
  115. package/src/game-xiyou/storage.ts +218 -0
  116. package/src/game-xiyou/treasure-system.ts +105 -0
  117. package/src/game-xiyou/types.ts +582 -0
  118. package/src/game-xiyou/uid-resolver.ts +49 -0
  119. package/src/gateway-methods.ts +740 -0
  120. package/src/onboarding.ts +553 -0
  121. package/src/policy.ts +32 -0
  122. package/src/probe.ts +210 -0
  123. package/src/reply-dispatcher.ts +874 -0
  124. package/src/runtime.ts +32 -0
  125. package/src/sdk/helpers.ts +322 -0
  126. package/src/sdk/types.ts +519 -0
  127. package/src/secret-input.ts +19 -0
  128. package/src/services/media/audio.ts +54 -0
  129. package/src/services/media/chunk-upload.ts +296 -0
  130. package/src/services/media/common.ts +155 -0
  131. package/src/services/media/file.ts +75 -0
  132. package/src/services/media/image.ts +81 -0
  133. package/src/services/media/index.ts +10 -0
  134. package/src/services/media/video.ts +162 -0
  135. package/src/services/media.ts +1143 -0
  136. package/src/services/messaging/card.ts +604 -0
  137. package/src/services/messaging/index.ts +18 -0
  138. package/src/services/messaging/mentions.ts +267 -0
  139. package/src/services/messaging/send.ts +141 -0
  140. package/src/services/messaging.ts +1191 -0
  141. package/src/services/reply-markers.ts +55 -0
  142. package/src/targets.ts +45 -0
  143. package/src/types/index.ts +59 -0
  144. package/src/types/pdf-parse.d.ts +3 -0
  145. package/src/utils/agent.ts +63 -0
  146. package/src/utils/async.ts +51 -0
  147. package/src/utils/constants.ts +27 -0
  148. package/src/utils/http-client.ts +38 -0
  149. package/src/utils/index.ts +8 -0
  150. package/src/utils/logger.ts +78 -0
  151. package/src/utils/session.ts +147 -0
  152. package/src/utils/token.ts +93 -0
  153. package/src/utils/utils-legacy.ts +454 -0
  154. package/tsconfig.json +20 -0
@@ -0,0 +1,423 @@
1
+ import { i as checkAndMarkDingtalkMessage } from "./utils-legacy-CALCPP1t.mjs";
2
+ import * as fs from "fs";
3
+ //#region src/core/connection.ts
4
+ /**
5
+ * 钉钉 WebSocket 连接层
6
+ *
7
+ * 职责:
8
+ * - 管理单个钉钉账号的 WebSocket 连接
9
+ * - 实现应用层心跳检测(10 秒间隔,90 秒超时)
10
+ * - 处理连接重连逻辑,带指数退避
11
+ * - 消息去重(内置 Map,5 分钟 TTL)
12
+ *
13
+ * 核心特性:
14
+ * - 关闭 SDK 内置 keepAlive,使用自定义心跳
15
+ * - 详细的消息接收日志(三阶段:接收、解析、处理)
16
+ * - 连接统计和监控(每分钟输出)
17
+ */
18
+ /** 心跳间隔(毫秒) */
19
+ const HEARTBEAT_INTERVAL = 10 * 1e3;
20
+ /** 超时阈值(毫秒) */
21
+ const TIMEOUT_THRESHOLD = 20 * 1e3;
22
+ /** 基础退避时间(毫秒) */
23
+ const BASE_BACKOFF_DELAY = 1e3;
24
+ /** 最大退避时间(毫秒) */
25
+ const MAX_BACKOFF_DELAY = 30 * 1e3;
26
+ async function monitorSingleAccount(opts) {
27
+ const { cfg, account, runtime, abortSignal, messageHandler, onStatusChange } = opts;
28
+ const { accountId } = account;
29
+ const clawdbotConfig = cfg;
30
+ const log = runtime?.log;
31
+ const { createLoggerFromConfig } = await import("./logger-BmJkQkm1.mjs");
32
+ const logger = createLoggerFromConfig(account.config, `DingTalk:${accountId}`);
33
+ if (!account.clientId || !account.clientSecret) throw new Error(`[DingTalk][${accountId}] Missing credentials: clientId=${account.clientId ? "present" : "MISSING"}, clientSecret=${account.clientSecret ? "present" : "MISSING"}. Please check your configuration in channels.dingtalk-connector.`);
34
+ const clientIdStr = String(account.clientId);
35
+ const clientSecretStr = String(account.clientSecret);
36
+ if (clientIdStr.length < 10 || clientSecretStr.length < 10) throw new Error(`[DingTalk][${accountId}] Invalid credentials format: clientId length=${clientIdStr.length}, clientSecret length=${clientSecretStr.length}. Credentials appear to be too short or invalid.`);
37
+ if (process.platform === "darwin") for (const stdioFd of [
38
+ 0,
39
+ 1,
40
+ 2
41
+ ]) try {
42
+ fs.fstatSync(stdioFd);
43
+ } catch (fdError) {
44
+ if (fdError.code === "EBADF") {
45
+ logger.warn(`[LaunchAgent] 检测到 fd ${stdioFd} 无效(EBADF),重定向到 /dev/null 以防止 TCP socket 创建失败`);
46
+ try {
47
+ fs.openSync("/dev/null", stdioFd === 0 ? "r" : "w");
48
+ } catch (openError) {
49
+ logger.warn(`[LaunchAgent] 无法修复 fd ${stdioFd}: ${openError.message}`);
50
+ }
51
+ }
52
+ }
53
+ logger.info(`Starting DingTalk Stream client...`);
54
+ logger.info(`Initializing with clientId: ${clientIdStr.substring(0, 8)}...`);
55
+ logger.info(`WebSocket keepAlive: false (using application-layer heartbeat)`);
56
+ const dingtalkStreamModule = await import("dingtalk-stream");
57
+ const DWClient = dingtalkStreamModule.DWClient;
58
+ const { TOPIC_ROBOT } = dingtalkStreamModule;
59
+ if (!DWClient) throw new Error("Failed to import DWClient from dingtalk-stream module");
60
+ const client = new DWClient({
61
+ clientId: account.clientId,
62
+ clientSecret: account.clientSecret,
63
+ debug: account.config.debug,
64
+ endpoint: account.config.endpoint || "https://api.dingtalk.com",
65
+ autoReconnect: false,
66
+ keepAlive: false
67
+ });
68
+ let lastSocketAvailableTime = Date.now();
69
+ let connectionEstablishedTime = Date.now();
70
+ let isReconnecting = false;
71
+ let reconnectAttempts = 0;
72
+ let keepAliveTimer = null;
73
+ let isStopped = false;
74
+ let activeMessageProcessing = false;
75
+ let messageProcessingKeepAliveTimer = null;
76
+ /**
77
+ * 标记消息处理开始,启动定期更新机制
78
+ * 在消息处理期间,每 30 秒更新一次 lastSocketAvailableTime
79
+ * 防止长时间处理(如复杂的 AI 任务)触发心跳超时
80
+ */
81
+ function markMessageProcessingStart() {
82
+ activeMessageProcessing = true;
83
+ lastSocketAvailableTime = Date.now();
84
+ if (messageProcessingKeepAliveTimer) clearInterval(messageProcessingKeepAliveTimer);
85
+ messageProcessingKeepAliveTimer = setInterval(() => {
86
+ if (activeMessageProcessing) {
87
+ lastSocketAvailableTime = Date.now();
88
+ logger.debug(`📝 消息处理中,更新 socket 可用时间`);
89
+ }
90
+ }, 30 * 1e3);
91
+ logger.debug(`📝 消息处理开始,启动活跃标记定时器`);
92
+ }
93
+ /**
94
+ * 标记消息处理结束,停止定期更新机制
95
+ */
96
+ function markMessageProcessingEnd() {
97
+ activeMessageProcessing = false;
98
+ if (messageProcessingKeepAliveTimer) {
99
+ clearInterval(messageProcessingKeepAliveTimer);
100
+ messageProcessingKeepAliveTimer = null;
101
+ }
102
+ lastSocketAvailableTime = Date.now();
103
+ logger.debug(`✅ 消息处理结束,清理活跃标记定时器`);
104
+ }
105
+ /** 计算指数退避延迟(带抖动) */
106
+ function calculateBackoffDelay(attempt) {
107
+ const exponentialDelay = BASE_BACKOFF_DELAY * Math.pow(2, attempt);
108
+ const jitter = Math.random() * 1e3;
109
+ return Math.min(exponentialDelay + jitter, MAX_BACKOFF_DELAY);
110
+ }
111
+ /** 统一重连函数,带指数退避(无限重连) */
112
+ async function doReconnect(immediate = false) {
113
+ if (isReconnecting || isStopped) {
114
+ logger.debug(`正在重连中或已停止,跳过`);
115
+ return;
116
+ }
117
+ isReconnecting = true;
118
+ if (!immediate && reconnectAttempts > 0) {
119
+ const delay = calculateBackoffDelay(reconnectAttempts);
120
+ logger.info(`⏳ 等待 ${Math.round(delay / 1e3)} 秒后重连 (尝试 ${reconnectAttempts + 1})`);
121
+ await new Promise((resolve) => setTimeout(resolve, delay));
122
+ }
123
+ try {
124
+ if (client.socket?.readyState === 1 || client.socket?.readyState === 3) {
125
+ await client.disconnect();
126
+ logger.info(`已断开旧连接`);
127
+ }
128
+ await client.connect();
129
+ if (!await new Promise((resolve) => {
130
+ const timeout = setTimeout(() => {
131
+ resolve(false);
132
+ }, 1e4);
133
+ if (client.socket?.readyState === 1) {
134
+ clearTimeout(timeout);
135
+ resolve(true);
136
+ return;
137
+ }
138
+ const onOpen = () => {
139
+ clearTimeout(timeout);
140
+ client.socket?.removeListener("open", onOpen);
141
+ client.socket?.removeListener("error", onError);
142
+ resolve(true);
143
+ };
144
+ const onError = (err) => {
145
+ clearTimeout(timeout);
146
+ client.socket?.removeListener("open", onOpen);
147
+ client.socket?.removeListener("error", onError);
148
+ logger.warn(`连接建立失败: ${err.message}`);
149
+ resolve(false);
150
+ };
151
+ client.socket?.once("open", onOpen);
152
+ client.socket?.once("error", onError);
153
+ })) throw new Error(`连接建立超时或失败`);
154
+ lastSocketAvailableTime = Date.now();
155
+ connectionEstablishedTime = Date.now();
156
+ reconnectAttempts = 0;
157
+ onStatusChange?.({
158
+ connected: true,
159
+ lastConnectedAt: Date.now()
160
+ });
161
+ setupPongListener();
162
+ setupMessageListener();
163
+ setupCloseListener();
164
+ logger.info(`✅ 重连成功 (socket 状态=${client.socket?.readyState})`);
165
+ } catch (err) {
166
+ reconnectAttempts++;
167
+ logger.error(`重连失败:${err.message} (尝试 ${reconnectAttempts})`);
168
+ throw err;
169
+ } finally {
170
+ isReconnecting = false;
171
+ }
172
+ }
173
+ /** 监听 pong 响应(更新 socket 可用时间) */
174
+ function setupPongListener() {
175
+ client.socket?.on("pong", () => {
176
+ lastSocketAvailableTime = Date.now();
177
+ logger.debug(`收到 PONG 响应`);
178
+ });
179
+ }
180
+ /** 监听 WebSocket message 事件,收到 disconnect 消息时立即触发重连 */
181
+ function setupMessageListener() {
182
+ client.socket?.on("message", (data) => {
183
+ try {
184
+ const msg = JSON.parse(data);
185
+ if (msg.type === "SYSTEM" && msg.headers?.topic === "disconnect") {
186
+ if (!isStopped && !isReconnecting) doReconnect(true).catch((err) => {
187
+ logger.error(`[${accountId}] 重连失败:${err.message}`);
188
+ });
189
+ }
190
+ } catch (e) {}
191
+ });
192
+ }
193
+ /** 监听 WebSocket close 事件,服务端主动断开时立即触发重连 */
194
+ function setupCloseListener() {
195
+ client.socket?.on("close", (code, reason) => {
196
+ logger.info(`WebSocket close: code=${code}, reason=${reason || "未知"}, isStopped=${isStopped}`);
197
+ onStatusChange?.({ connected: false });
198
+ if (isStopped) return;
199
+ setTimeout(() => {
200
+ doReconnect(true).catch((err) => {
201
+ logger.error(`重连失败:${err.message}`);
202
+ });
203
+ }, 0);
204
+ });
205
+ }
206
+ /**
207
+ * 启动 keepAlive 机制(单定时器 + 指数退避)
208
+ *
209
+ * 业界最佳实践:
210
+ * - 单定时器:每 10 秒检查一次,同时完成心跳和超时检测
211
+ * - 使用 WebSocket 原生 Ping
212
+ * - 指数退避重连:避免雪崩效应
213
+ */
214
+ function startKeepAlive() {
215
+ logger.debug(`🚀 启动 keepAlive 定时器,间隔=${HEARTBEAT_INTERVAL / 1e3}秒`);
216
+ keepAliveTimer = setInterval(async () => {
217
+ if (isStopped) {
218
+ if (keepAliveTimer) clearInterval(keepAliveTimer);
219
+ return;
220
+ }
221
+ try {
222
+ const elapsed = Date.now() - lastSocketAvailableTime;
223
+ if (elapsed > TIMEOUT_THRESHOLD) {
224
+ logger.info(`⚠️ 超时检测:已 ${Math.round(elapsed / 1e3)} 秒未确认 socket 可用,触发重连...`);
225
+ await doReconnect();
226
+ return;
227
+ }
228
+ const socketState = client.socket?.readyState;
229
+ const timeSinceConnection = Date.now() - connectionEstablishedTime;
230
+ logger.debug(`心跳检测:socket 状态=${socketState}, elapsed=${Math.round(elapsed / 1e3)}s, 连接已建立=${Math.round(timeSinceConnection / 1e3)}s`);
231
+ if (socketState !== 1) {
232
+ if (timeSinceConnection < 15e3) {
233
+ logger.debug(`⏳ 连接建立中(已 ${Math.round(timeSinceConnection / 1e3)}s),跳过状态检查`);
234
+ return;
235
+ }
236
+ logger.info(`⚠️ 心跳检测:socket 状态=${socketState},触发重连...`);
237
+ await doReconnect(true);
238
+ return;
239
+ }
240
+ try {
241
+ client.socket?.ping();
242
+ logger.debug(`💓 发送 PING 心跳成功`);
243
+ } catch (err) {
244
+ logger.warn(`发送 PING 失败:${err.message}`);
245
+ }
246
+ } catch (err) {
247
+ logger.error(`keepAlive 检测失败:${err.message}`);
248
+ }
249
+ }, HEARTBEAT_INTERVAL);
250
+ logger.debug(`✅ keepAlive 定时器已启动`);
251
+ return () => {
252
+ if (keepAliveTimer) clearInterval(keepAliveTimer);
253
+ keepAliveTimer = null;
254
+ logger.debug(`keepAlive 定时器已清理`);
255
+ };
256
+ }
257
+ /** 停止并清理所有资源 */
258
+ function stop() {
259
+ isStopped = true;
260
+ if (keepAliveTimer) clearInterval(keepAliveTimer);
261
+ keepAliveTimer = null;
262
+ if (messageProcessingKeepAliveTimer) {
263
+ clearInterval(messageProcessingKeepAliveTimer);
264
+ messageProcessingKeepAliveTimer = null;
265
+ }
266
+ if (client.socket) client.socket.removeAllListeners();
267
+ logger.debug(`Connection 已停止`);
268
+ }
269
+ return new Promise(async (resolve, reject) => {
270
+ if (abortSignal) {
271
+ const onAbort = async () => {
272
+ logger.info(`Abort signal received, stopping...`);
273
+ stop();
274
+ try {
275
+ if (client.socket && client.socket.readyState === 1) await client.disconnect();
276
+ } catch (err) {
277
+ logger.warn(`断开连接时出错:${err.message}`);
278
+ }
279
+ resolve();
280
+ };
281
+ abortSignal.addEventListener("abort", onAbort, { once: true });
282
+ }
283
+ let receivedCount = 0;
284
+ let processedCount = 0;
285
+ let lastMessageTime = Date.now();
286
+ const statsInterval = setInterval(() => {
287
+ const timeSinceLastMessage = Math.round((Date.now() - lastMessageTime) / 1e3);
288
+ logger.info(`统计:收到=${receivedCount}, 处理=${processedCount}, 丢失=${receivedCount - processedCount}, 距上次消息=${timeSinceLastMessage}s`);
289
+ }, 6e4);
290
+ client.registerCallbackListener(TOPIC_ROBOT, async (res) => {
291
+ receivedCount++;
292
+ lastMessageTime = Date.now();
293
+ onStatusChange?.({ lastInboundAt: Date.now() });
294
+ const messageId = res.headers?.messageId;
295
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
296
+ logger.info(`\n========== 收到新消息 ==========`);
297
+ logger.info(`时间:${timestamp}`);
298
+ logger.info(`MessageId: ${messageId || "N/A"}`);
299
+ logger.info(`Headers: ${JSON.stringify(res.headers || {})}`);
300
+ logger.info(`Data 长度:${res.data?.length || 0} 字符`);
301
+ if (messageId) {
302
+ client.socketCallBackResponse(messageId, { success: true });
303
+ logger.info(`✅ 已立即确认回调:messageId=${messageId}`);
304
+ } else logger.warn(`⚠️ 警告:消息没有 messageId`);
305
+ if (messageId && checkAndMarkDingtalkMessage(accountId, messageId, void 0)) {
306
+ processedCount++;
307
+ logger.warn(`⚠️ 检测到重复消息(协议层),跳过处理:messageId=${messageId} (${processedCount}/${receivedCount})`);
308
+ logger.info(`========== 消息处理结束(重复) ==========\n`);
309
+ return;
310
+ }
311
+ markMessageProcessingStart();
312
+ try {
313
+ let data;
314
+ try {
315
+ data = JSON.parse(res.data);
316
+ } catch (parseError) {
317
+ logger.error("Failed to parse response data as JSON:", {
318
+ error: parseError instanceof Error ? parseError.message : String(parseError),
319
+ rawData: typeof res.data === "string" ? res.data.substring(0, 500) : res.data,
320
+ dataType: typeof res.data
321
+ });
322
+ throw new Error(`Invalid JSON response from DingTalk API. Error: ${parseError instanceof Error ? parseError.message : String(parseError)}. Raw data (first 100 chars): ${String(res.data).substring(0, 100)}`);
323
+ }
324
+ logger.info(`\n----- 消息详情 -----`);
325
+ logger.info(`消息类型:${data.msgtype || "unknown"}`);
326
+ logger.info(`会话类型:${data.conversationType === "1" ? "DM (单聊)" : data.conversationType === "2" ? "Group (群聊)" : data.conversationType}`);
327
+ logger.info(`发送者:${data.senderNick || "unknown"} (${data.senderStaffId || data.senderId || "unknown"})`);
328
+ logger.info(`会话 ID: ${data.conversationId || "N/A"}`);
329
+ logger.info(`消息 ID: ${data.msgId || "N/A"}`);
330
+ logger.info(`SessionWebhook: ${data.sessionWebhook ? "已提供" : "未提供"}`);
331
+ logger.info(`RobotCode: ${data.robotCode || account.config?.clientId || "N/A"}`);
332
+ if (data.chatbotUserId || data.chatbotCorpId) console.log(`[DingTalk:${accountId}] [BotIdentity] accountId=${accountId} chatbotUserId=${data.chatbotUserId || "N/A"} chatbotCorpId=${data.chatbotCorpId || "N/A"}`);
333
+ data.msgId;
334
+ let contentPreview = "N/A";
335
+ if (data.text?.content) contentPreview = data.text.content.length > 100 ? data.text.content.substring(0, 100) + "..." : data.text.content;
336
+ else if (data.content) contentPreview = JSON.stringify(data.content).substring(0, 100) + "...";
337
+ logger.info(`消息内容预览:${contentPreview}`);
338
+ logger.info(`完整数据字段:${Object.keys(data).join(", ")}`);
339
+ logger.info(`----- 消息详情结束 -----\n`);
340
+ logger.info(`🚀 开始处理消息...`);
341
+ await messageHandler({
342
+ accountId,
343
+ config: account.config,
344
+ data,
345
+ sessionWebhook: data.sessionWebhook,
346
+ runtime,
347
+ log,
348
+ cfg: clawdbotConfig
349
+ });
350
+ processedCount++;
351
+ logger.info(`✅ 消息处理完成 (${processedCount}/${receivedCount})`);
352
+ logger.info(`========== 消息处理结束(成功) ==========\n`);
353
+ } catch (error) {
354
+ processedCount++;
355
+ const errorMsg = `❌ 处理消息异常 (${processedCount}/${receivedCount}): ${error?.message || "未知错误"}`;
356
+ const errorStack = error?.stack || "无堆栈信息";
357
+ logger.error(errorMsg);
358
+ logger.error(`错误堆栈:\n${errorStack}`);
359
+ logger.info(`========== 消息处理结束(失败) ==========\n`);
360
+ } finally {
361
+ markMessageProcessingEnd();
362
+ }
363
+ });
364
+ const cleanup = () => {
365
+ clearInterval(statsInterval);
366
+ stop();
367
+ };
368
+ try {
369
+ await client.connect();
370
+ setupPongListener();
371
+ setupMessageListener();
372
+ setupCloseListener();
373
+ logger.info(`Connected to DingTalk Stream successfully`);
374
+ logger.info(`PID: ${process.pid}`);
375
+ logger.info(`✅ 自定义 keepAlive: true (10 秒心跳,90 秒超时), 指数退避重连`);
376
+ onStatusChange?.({
377
+ connected: true,
378
+ lastConnectedAt: Date.now()
379
+ });
380
+ const cleanupKeepAlive = startKeepAlive();
381
+ const enhancedCleanup = () => {
382
+ cleanupKeepAlive();
383
+ clearInterval(statsInterval);
384
+ stop();
385
+ };
386
+ process.once("exit", enhancedCleanup);
387
+ process.once("SIGINT", enhancedCleanup);
388
+ process.once("SIGTERM", enhancedCleanup);
389
+ } catch (error) {
390
+ cleanup();
391
+ logger.info(`连接失败,错误详情:`);
392
+ logger.info(` - error.message: ${error.message}`);
393
+ logger.info(` - error.response?.status: ${error.response?.status}`);
394
+ logger.info(` - error.response?.data: ${JSON.stringify(error.response?.data || {})}`);
395
+ logger.info(` - error.code: ${error.code}`);
396
+ logger.info(` - error.stack: ${error.stack?.split("\n").slice(0, 3).join("\n")}`);
397
+ if (error.response?.status === 400 || error.message?.includes("status code 400") || error.message?.includes("400")) {
398
+ reject(/* @__PURE__ */ new Error(`[DingTalk][${accountId}] Bad Request (400):\n - clientId or clientSecret format is invalid\n - clientId: ${clientIdStr} (type: ${typeof account.clientId}, length: ${clientIdStr.length})\n - clientSecret: ****** (type: ${typeof account.clientSecret}, length: ${clientSecretStr.length})\n - Common issues:\n 1. clientId/clientSecret must be strings, not numbers\n 2. Remove any quotes or special characters\n 3. Ensure credentials are from the correct DingTalk app\n 4. Check if clientId starts with 'ding' prefix\n - Error details: ${error.message}\n - Response data: ${JSON.stringify(error.response?.data || {})}`));
399
+ return;
400
+ }
401
+ if (error.response?.status === 401 || error.message?.includes("401")) {
402
+ reject(/* @__PURE__ */ new Error(`[DingTalk][${accountId}] Authentication failed (401 Unauthorized):\n - Your clientId or clientSecret is invalid, expired, or revoked\n - clientId: ${clientIdStr.substring(0, 8)}...\n - Please verify your credentials at DingTalk Developer Console\n - Error details: ${error.message}`));
403
+ return;
404
+ }
405
+ reject(/* @__PURE__ */ new Error(`[DingTalk][${accountId}] Failed to connect to DingTalk Stream: ${error.message}`));
406
+ return;
407
+ }
408
+ client.on("error", (err) => {
409
+ logger.error(`Connection error: ${err.message}`);
410
+ });
411
+ client.on("reconnect", () => {
412
+ logger.info(`SDK reconnecting...`);
413
+ });
414
+ client.on("reconnected", () => {
415
+ logger.info(`✅ SDK reconnected successfully`);
416
+ });
417
+ });
418
+ }
419
+ function resolveReactionSyntheticEvent(event) {
420
+ return null;
421
+ }
422
+ //#endregion
423
+ export { monitorSingleAccount, resolveReactionSyntheticEvent };
@@ -0,0 +1,16 @@
1
+ import * as _$openclaw_plugin_sdk_channel_entry_contract0 from "openclaw/plugin-sdk/channel-entry-contract";
2
+ import * as _$openclaw_plugin_sdk0 from "openclaw/plugin-sdk";
3
+
4
+ //#region entry-bundled.d.ts
5
+ /**
6
+ * Bundled entry for openclaw-fork compatibility.
7
+ *
8
+ * Standard openclaw loads the plugin via `index.ts` (export default register).
9
+ * openclaw-fork expects `defineBundledChannelEntry` format.
10
+ *
11
+ * Usage in package.json exports:
12
+ * "./bundled" → this file
13
+ */
14
+ declare const _default: _$openclaw_plugin_sdk_channel_entry_contract0.BundledChannelEntryContract<_$openclaw_plugin_sdk0.ChannelPlugin>;
15
+ //#endregion
16
+ export { _default as default };
@@ -0,0 +1,31 @@
1
+ import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract";
2
+ //#region entry-bundled.ts
3
+ /**
4
+ * Bundled entry for openclaw-fork compatibility.
5
+ *
6
+ * Standard openclaw loads the plugin via `index.ts` (export default register).
7
+ * openclaw-fork expects `defineBundledChannelEntry` format.
8
+ *
9
+ * Usage in package.json exports:
10
+ * "./bundled" → this file
11
+ */
12
+ var entry_bundled_default = defineBundledChannelEntry({
13
+ id: "dingtalk-connector",
14
+ name: "DingTalk",
15
+ description: "DingTalk (钉钉) channel connector — Stream mode with AI Card streaming",
16
+ importMetaUrl: import.meta.url,
17
+ plugin: {
18
+ specifier: "./index.ts",
19
+ exportName: "dingtalkPlugin"
20
+ },
21
+ runtime: {
22
+ specifier: "./index.ts",
23
+ exportName: "setDingtalkRuntime"
24
+ },
25
+ async registerFull(api) {
26
+ const { registerGatewayMethods } = await import("./gateway-methods-Ci31A3vg.mjs");
27
+ registerGatewayMethods(api);
28
+ }
29
+ });
30
+ //#endregion
31
+ export { entry_bundled_default as default };