@jeik/dingtalk-connector 0.8.27 → 0.8.29
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 +48 -0
- package/README.en.md +40 -82
- package/README.md +50 -93
- package/dist/background-work-DkaSDqWk.mjs +53 -0
- package/dist/card-content-cache-C7-XHhHw.mjs +2 -0
- package/dist/{card-content-cache-CYUzCaQJ.mjs → card-content-cache-DT_OYThh.mjs} +15 -1
- package/dist/chunk-upload-BKM1y4Ba.mjs +2 -0
- package/dist/{chunk-upload-BBQgGtcZ.mjs → chunk-upload-CIU5G_ay.mjs} +1 -1
- package/dist/{common-DE0gd0Jg.mjs → common-BfIYs1yC.mjs} +3 -3
- package/dist/{common-B_A78n8u.mjs → common-C0Oaf0z-.mjs} +1 -1
- package/dist/connection-obtrPkj4.mjs +641 -0
- package/dist/entry-bundled.mjs +1 -1
- package/dist/{gateway-methods--aKsyMs-.mjs → gateway-methods-BdcpfxEZ.mjs} +1 -1
- package/dist/gateway-methods-DQ6TwOAr.mjs +2 -0
- package/dist/index.mjs +2 -2
- package/dist/{logger-mZ9OSbmD.mjs → logger-CnBTcwyq.mjs} +16 -2
- package/dist/logger-D8H2IcYz.mjs +2 -0
- package/dist/{media-BqpUGCni.mjs → media-DDtX37T2.mjs} +1 -1
- package/dist/{media-DnQB2BrL.mjs → media-g8tJtpfU.mjs} +29 -20
- package/dist/{message-handler-DQZiKx3i.mjs → message-handler-BrO2i7tg.mjs} +251 -139
- package/dist/{messaging-WleKf54h.mjs → messaging-CK0Wz3Ze.mjs} +23 -21
- package/dist/{runtime-DzZo6Gvk.mjs → runtime-DbqGRyhI.mjs} +6 -5
- package/dist/{utils-Bjh4r_qS.mjs → utils-Bwz2Fls_.mjs} +1 -1
- package/docs/RELEASE_NOTES_V0.8.28.md +56 -0
- package/docs/RELEASE_NOTES_V0.8.29.md +57 -0
- package/openclaw.plugin.json +9 -5
- package/package.json +2 -2
- package/src/config/schema.ts +2 -1
- package/src/core/connection.ts +504 -220
- package/src/core/message-handler.ts +197 -102
- package/src/reply-dispatcher.ts +244 -93
- package/src/services/media.ts +60 -27
- package/src/services/messaging/card-content-cache.ts +11 -6
- package/src/services/messaging/card.ts +2 -0
- package/src/services/messaging.ts +52 -38
- package/src/utils/background-work.ts +65 -0
- package/src/utils/logger.ts +23 -2
- package/dist/card-content-cache-Dwd8HICP.mjs +0 -2
- package/dist/chunk-upload-DaLXXZH3.mjs +0 -2
- package/dist/connection-fc6B4z4G.mjs +0 -423
- package/dist/gateway-methods-BQd4E0Sk.mjs +0 -2
- package/dist/logger-BmJkQkm1.mjs +0 -2
- /package/dist/{game-xiyou-CAT0ilSj.mjs → game-xiyou-uwsv23AC.mjs} +0 -0
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
import { i as checkAndMarkDingtalkMessage } from "./utils-legacy-DQ3HeGHh.mjs";
|
|
2
|
+
import { n as onBackgroundWorkCountChange, t as getActiveBackgroundWorkCount } from "./background-work-DkaSDqWk.mjs";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
//#region src/core/connection.ts
|
|
5
|
+
/**
|
|
6
|
+
* 钉钉 WebSocket 连接层
|
|
7
|
+
*
|
|
8
|
+
* 职责:
|
|
9
|
+
* - 管理单个钉钉账号的 WebSocket 连接
|
|
10
|
+
* - 实现应用层心跳检测(10 秒间隔,20 秒无 pong 超时)
|
|
11
|
+
* - 处理连接重连逻辑,带指数退避
|
|
12
|
+
* - 消息去重(内置 Map,5 分钟 TTL)
|
|
13
|
+
*
|
|
14
|
+
* 核心特性:
|
|
15
|
+
* - 关闭 SDK 内置 keepAlive,使用自定义心跳
|
|
16
|
+
* - connect 后等 OPEN + SYSTEM/REGISTERED 再报 ready(防首条消息丢失)
|
|
17
|
+
* - 重连后先挂 pong/message/close listener 再等就绪(防幽灵重连)
|
|
18
|
+
* - 详细的消息接收日志(三阶段:接收、解析、处理)
|
|
19
|
+
* - 连接统计和监控(每分钟输出)
|
|
20
|
+
*/
|
|
21
|
+
/** 心跳间隔(毫秒) */
|
|
22
|
+
const HEARTBEAT_INTERVAL = 10 * 1e3;
|
|
23
|
+
/**
|
|
24
|
+
* 无 pong 软超时:仅计「连续未确认」次数,不立刻 disconnect。
|
|
25
|
+
* 过去这里直接 doReconnect(),会在 socket 仍 OPEN 时拆掉活连接,
|
|
26
|
+
* 造成数秒空窗 → 用户连发 3~7 条消息钉钉推不到本进程(网关 UI 完全收不到)。
|
|
27
|
+
*/
|
|
28
|
+
const SOFT_STALE_MS = 20 * 1e3;
|
|
29
|
+
/** 连续软超时次数达到该值,且 socket 非 OPEN 或 ping 失败,才硬重连 */
|
|
30
|
+
const HARD_RECONNECT_AFTER_MISSES = 3;
|
|
31
|
+
/** 两次硬重连之间的最小间隔(除非 socket 已死) */
|
|
32
|
+
const MIN_RECONNECT_GAP_MS = 30 * 1e3;
|
|
33
|
+
/**
|
|
34
|
+
* 消息处理期间刷新 lastSocketAvailableTime 的间隔。
|
|
35
|
+
* 必须明显小于 SOFT_STALE_MS。
|
|
36
|
+
*/
|
|
37
|
+
const MESSAGE_PROCESSING_KEEPALIVE_MS = 15 * 1e3;
|
|
38
|
+
/** 基础退避时间(毫秒) */
|
|
39
|
+
const BASE_BACKOFF_DELAY = 1e3;
|
|
40
|
+
/** 最大退避时间(毫秒) */
|
|
41
|
+
const MAX_BACKOFF_DELAY = 30 * 1e3;
|
|
42
|
+
/** 单次等待 OPEN + REGISTERED 的超时 */
|
|
43
|
+
const STREAM_READY_TIMEOUT_MS = 15e3;
|
|
44
|
+
/**
|
|
45
|
+
* REGISTERED 未就绪时最多完整重连次数(含首次)。
|
|
46
|
+
* 超时禁止 OPEN-only 假 ready,避免「显示已连接但收不到 CALLBACK」。
|
|
47
|
+
*/
|
|
48
|
+
const REGISTERED_CONNECT_MAX_ATTEMPTS = 5;
|
|
49
|
+
let _streamNoiseSilenced = false;
|
|
50
|
+
function silenceDingtalkStreamConsoleNoise() {
|
|
51
|
+
if (_streamNoiseSilenced) return;
|
|
52
|
+
_streamNoiseSilenced = true;
|
|
53
|
+
const origConsoleInfo = console.info.bind(console);
|
|
54
|
+
console.info = (...args) => {
|
|
55
|
+
const first = args[0];
|
|
56
|
+
if (typeof first === "string") {
|
|
57
|
+
if (first === "Disconnecting.") return;
|
|
58
|
+
if (/^\[[^\]]+\] connect success$/.test(first)) return;
|
|
59
|
+
}
|
|
60
|
+
return origConsoleInfo(...args);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
let _connectionNoticePrinted = false;
|
|
64
|
+
function printConnectionNoticeOnce() {
|
|
65
|
+
if (_connectionNoticePrinted) return;
|
|
66
|
+
_connectionNoticePrinted = true;
|
|
67
|
+
console.log("[dingtalk-connector] ℹ️ 上游 dingtalk-stream 噪音已过滤;OPEN 成功即可上线,REGISTERED 尽量等待;若环境不推 REGISTERED,首条消息会确认订阅。正常运行不应出现 ≤30s 周期性硬重连。");
|
|
68
|
+
}
|
|
69
|
+
async function monitorSingleAccount(opts) {
|
|
70
|
+
const { cfg, account, runtime, abortSignal, messageHandler, onStatusChange } = opts;
|
|
71
|
+
const { accountId } = account;
|
|
72
|
+
silenceDingtalkStreamConsoleNoise();
|
|
73
|
+
const clawdbotConfig = cfg;
|
|
74
|
+
const log = runtime?.log;
|
|
75
|
+
const { createLoggerFromConfig, setDingtalkDebug } = await import("./logger-D8H2IcYz.mjs");
|
|
76
|
+
if (account.config?.debug) setDingtalkDebug(true);
|
|
77
|
+
const logger = createLoggerFromConfig(account.config, `DingTalk:${accountId}`);
|
|
78
|
+
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.`);
|
|
79
|
+
const clientIdStr = String(account.clientId);
|
|
80
|
+
const clientSecretStr = String(account.clientSecret);
|
|
81
|
+
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.`);
|
|
82
|
+
if (process.platform === "darwin") for (const stdioFd of [
|
|
83
|
+
0,
|
|
84
|
+
1,
|
|
85
|
+
2
|
|
86
|
+
]) try {
|
|
87
|
+
fs.fstatSync(stdioFd);
|
|
88
|
+
} catch (fdError) {
|
|
89
|
+
if (fdError.code === "EBADF") {
|
|
90
|
+
logger.warn(`[LaunchAgent] 检测到 fd ${stdioFd} 无效(EBADF),重定向到 /dev/null 以防止 TCP socket 创建失败`);
|
|
91
|
+
try {
|
|
92
|
+
fs.openSync("/dev/null", stdioFd === 0 ? "r" : "w");
|
|
93
|
+
} catch (openError) {
|
|
94
|
+
logger.warn(`[LaunchAgent] 无法修复 fd ${stdioFd}: ${openError.message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
logger.info(`Starting DingTalk Stream client...`);
|
|
99
|
+
logger.info(`Initializing with clientId: ${clientIdStr.substring(0, 8)}...`);
|
|
100
|
+
logger.info(`WebSocket keepAlive: false (using application-layer heartbeat)`);
|
|
101
|
+
const dingtalkStreamModule = await import("dingtalk-stream");
|
|
102
|
+
const DWClient = dingtalkStreamModule.DWClient;
|
|
103
|
+
const { TOPIC_ROBOT } = dingtalkStreamModule;
|
|
104
|
+
if (!DWClient) throw new Error("Failed to import DWClient from dingtalk-stream module");
|
|
105
|
+
const client = new DWClient({
|
|
106
|
+
clientId: account.clientId,
|
|
107
|
+
clientSecret: account.clientSecret,
|
|
108
|
+
debug: account.config.debug,
|
|
109
|
+
endpoint: account.config.endpoint || "https://api.dingtalk.com",
|
|
110
|
+
autoReconnect: false,
|
|
111
|
+
keepAlive: false
|
|
112
|
+
});
|
|
113
|
+
let lastSocketAvailableTime = Date.now();
|
|
114
|
+
let connectionEstablishedTime = Date.now();
|
|
115
|
+
let isReconnecting = false;
|
|
116
|
+
let reconnectAttempts = 0;
|
|
117
|
+
let keepAliveTimer = null;
|
|
118
|
+
let isStopped = false;
|
|
119
|
+
/** 连续 keepAlive 周期内未收到 pong / 未刷新的次数 */
|
|
120
|
+
let consecutiveStaleMisses = 0;
|
|
121
|
+
let lastHardReconnectAt = 0;
|
|
122
|
+
let wsCallbackActive = false;
|
|
123
|
+
let activeMessageProcessing = false;
|
|
124
|
+
let messageProcessingKeepAliveTimer = null;
|
|
125
|
+
let unsubscribeBackgroundWork = null;
|
|
126
|
+
function ensureProcessingKeepAliveTimer() {
|
|
127
|
+
if (messageProcessingKeepAliveTimer) return;
|
|
128
|
+
messageProcessingKeepAliveTimer = setInterval(() => {
|
|
129
|
+
if (activeMessageProcessing) {
|
|
130
|
+
lastSocketAvailableTime = Date.now();
|
|
131
|
+
logger.debug(`📝 消息处理中,更新 socket 可用时间 (ws=${wsCallbackActive} bg=${getActiveBackgroundWorkCount()})`);
|
|
132
|
+
}
|
|
133
|
+
}, MESSAGE_PROCESSING_KEEPALIVE_MS);
|
|
134
|
+
}
|
|
135
|
+
function clearProcessingKeepAliveTimer() {
|
|
136
|
+
if (messageProcessingKeepAliveTimer) {
|
|
137
|
+
clearInterval(messageProcessingKeepAliveTimer);
|
|
138
|
+
messageProcessingKeepAliveTimer = null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** 根据 WS 入队中 / 后台任务数 合成 activeMessageProcessing */
|
|
142
|
+
function refreshProcessingActive(reason) {
|
|
143
|
+
const bg = getActiveBackgroundWorkCount();
|
|
144
|
+
const next = wsCallbackActive || bg > 0;
|
|
145
|
+
activeMessageProcessing = next;
|
|
146
|
+
lastSocketAvailableTime = Date.now();
|
|
147
|
+
if (next) {
|
|
148
|
+
ensureProcessingKeepAliveTimer();
|
|
149
|
+
logger.debug(`📝 处理活跃 reason=${reason} ws=${wsCallbackActive} bg=${bg}`);
|
|
150
|
+
} else {
|
|
151
|
+
clearProcessingKeepAliveTimer();
|
|
152
|
+
logger.debug(`✅ 处理空闲 reason=${reason}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function markMessageProcessingStart() {
|
|
156
|
+
wsCallbackActive = true;
|
|
157
|
+
refreshProcessingActive("ws-start");
|
|
158
|
+
}
|
|
159
|
+
function markMessageProcessingEnd() {
|
|
160
|
+
wsCallbackActive = false;
|
|
161
|
+
refreshProcessingActive("ws-end");
|
|
162
|
+
}
|
|
163
|
+
unsubscribeBackgroundWork = onBackgroundWorkCountChange(() => {
|
|
164
|
+
refreshProcessingActive("bg-count");
|
|
165
|
+
});
|
|
166
|
+
/** 计算指数退避延迟(带抖动) */
|
|
167
|
+
function calculateBackoffDelay(attempt) {
|
|
168
|
+
const exponentialDelay = BASE_BACKOFF_DELAY * Math.pow(2, attempt);
|
|
169
|
+
const jitter = Math.random() * 1e3;
|
|
170
|
+
return Math.min(exponentialDelay + jitter, MAX_BACKOFF_DELAY);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 等待钉钉 Stream 可服务。
|
|
174
|
+
*
|
|
175
|
+
* - OPEN:必须成功,否则 throw
|
|
176
|
+
* - REGISTERED:尽量等待;超时则 **降级 OPEN 上线**(不 throw)
|
|
177
|
+
*
|
|
178
|
+
* 原因(生产日志):部分环境 socket 已 OPEN 且 CALLBACK 能收消息,
|
|
179
|
+
* 但长期收不到 SYSTEM/REGISTERED 或 client.registered 不被置 true。
|
|
180
|
+
* 若硬性失败会触发网关 auto-restart 风暴,而旧连接/半连接其实仍在处理消息。
|
|
181
|
+
*/
|
|
182
|
+
async function waitForStreamReady(timeoutMs = STREAM_READY_TIMEOUT_MS) {
|
|
183
|
+
const started = Date.now();
|
|
184
|
+
if (client.socket?.readyState !== 1) {
|
|
185
|
+
if (!await new Promise((resolve) => {
|
|
186
|
+
const socket = client.socket;
|
|
187
|
+
if (!socket) {
|
|
188
|
+
resolve(false);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (socket.readyState === 1) {
|
|
192
|
+
resolve(true);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const remain = Math.max(1e3, timeoutMs - (Date.now() - started));
|
|
196
|
+
let settled = false;
|
|
197
|
+
const finish = (ok) => {
|
|
198
|
+
if (settled) return;
|
|
199
|
+
settled = true;
|
|
200
|
+
clearTimeout(timer);
|
|
201
|
+
socket.removeListener?.("open", onOpen);
|
|
202
|
+
socket.removeListener?.("error", onError);
|
|
203
|
+
resolve(ok);
|
|
204
|
+
};
|
|
205
|
+
const timer = setTimeout(() => finish(false), remain);
|
|
206
|
+
const onOpen = () => finish(true);
|
|
207
|
+
const onError = () => finish(false);
|
|
208
|
+
socket.once("open", onOpen);
|
|
209
|
+
socket.once("error", onError);
|
|
210
|
+
})) throw new Error(`WebSocket OPEN 超时(${timeoutMs}ms)`);
|
|
211
|
+
}
|
|
212
|
+
if (client.registered === true) {
|
|
213
|
+
logger.info(`✅ Stream 已就绪(OPEN + REGISTERED),耗时 ${Date.now() - started}ms`);
|
|
214
|
+
return { registered: true };
|
|
215
|
+
}
|
|
216
|
+
if (await new Promise((resolve) => {
|
|
217
|
+
const socket = client.socket;
|
|
218
|
+
if (!socket) {
|
|
219
|
+
resolve(false);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const remain = Math.max(1e3, timeoutMs - (Date.now() - started));
|
|
223
|
+
let settled = false;
|
|
224
|
+
const finish = (ok) => {
|
|
225
|
+
if (settled) return;
|
|
226
|
+
settled = true;
|
|
227
|
+
clearTimeout(timer);
|
|
228
|
+
clearInterval(poll);
|
|
229
|
+
socket.removeListener?.("message", onMessage);
|
|
230
|
+
resolve(ok);
|
|
231
|
+
};
|
|
232
|
+
const timer = setTimeout(() => finish(false), remain);
|
|
233
|
+
const onMessage = (data) => {
|
|
234
|
+
try {
|
|
235
|
+
const raw = typeof data === "string" ? data : data?.toString?.() ?? String(data);
|
|
236
|
+
const msg = JSON.parse(raw);
|
|
237
|
+
if (msg?.type === "SYSTEM" && msg?.headers?.topic === "REGISTERED") {
|
|
238
|
+
finish(true);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
} catch {}
|
|
242
|
+
if (client.registered === true) finish(true);
|
|
243
|
+
};
|
|
244
|
+
const poll = setInterval(() => {
|
|
245
|
+
if (client.registered === true) finish(true);
|
|
246
|
+
}, 100);
|
|
247
|
+
socket.on("message", onMessage);
|
|
248
|
+
}) || client.registered === true) {
|
|
249
|
+
logger.info(`✅ Stream 已就绪(OPEN + REGISTERED),耗时 ${Date.now() - started}ms`);
|
|
250
|
+
return { registered: true };
|
|
251
|
+
}
|
|
252
|
+
logger.warn(`⚠️ 未观察到 SYSTEM/REGISTERED(${timeoutMs}ms),socket 已 OPEN → 降级上线。 收到首条机器人消息后会记为「订阅 empirically 生效」。`);
|
|
253
|
+
return { registered: false };
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* connect 之后、wait ready 之前挂上 pong/message/close。
|
|
257
|
+
* 官方 #566:若在 connect 前 setup,socket 为 undefined,listener 静默 no-op →
|
|
258
|
+
* pong 无人接 → lastSocketAvailableTime 不刷新 → TIMEOUT 幽灵重连。
|
|
259
|
+
* 必须在 wait OPEN 之前挂好,否则等待窗内的 pong 也会丢。
|
|
260
|
+
*/
|
|
261
|
+
function attachSocketLifecycleListeners() {
|
|
262
|
+
setupPongListener();
|
|
263
|
+
setupMessageListener();
|
|
264
|
+
setupCloseListener();
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* 单次:connect + 挂 listener + 等 OPEN(REGISTERED 可选)。
|
|
268
|
+
* 仅 OPEN 失败 throw;REGISTERED 超时则降级成功。
|
|
269
|
+
*/
|
|
270
|
+
async function connectAndWaitReady() {
|
|
271
|
+
await client.connect();
|
|
272
|
+
attachSocketLifecycleListeners();
|
|
273
|
+
return await waitForStreamReady(STREAM_READY_TIMEOUT_MS);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* 建立可服务连接。
|
|
277
|
+
* - 优先拿到 REGISTERED
|
|
278
|
+
* - OPEN 成功即可上线(避免环境不推 REGISTERED 时 restart 风暴)
|
|
279
|
+
* - 仅在 OPEN 都失败时多轮重试
|
|
280
|
+
*/
|
|
281
|
+
async function ensureRegisteredConnection(maxAttempts = REGISTERED_CONNECT_MAX_ATTEMPTS) {
|
|
282
|
+
let lastError;
|
|
283
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
284
|
+
if (isStopped) throw new Error("连接已停止,中止 Stream 建立");
|
|
285
|
+
try {
|
|
286
|
+
if (attempt > 1) {
|
|
287
|
+
logger.warn(`🔄 Stream 建立重试 ${attempt}/${maxAttempts}…`);
|
|
288
|
+
try {
|
|
289
|
+
if (client.socket) await client.disconnect();
|
|
290
|
+
} catch (discErr) {
|
|
291
|
+
logger.debug(`断开旧连接: ${discErr?.message || discErr}`);
|
|
292
|
+
}
|
|
293
|
+
const delay = Math.min(1e3 * attempt, 5e3);
|
|
294
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
295
|
+
} else logger.info(`⏳ 建立 Stream(OPEN 必须;REGISTERED 尽量等待 ${STREAM_READY_TIMEOUT_MS}ms)…`);
|
|
296
|
+
const ready = await connectAndWaitReady();
|
|
297
|
+
noteSocketAlive(ready.registered ? "registered-ok" : "open-degraded");
|
|
298
|
+
connectionEstablishedTime = Date.now();
|
|
299
|
+
logger.info(`✅ Stream 可服务(attempt ${attempt}/${maxAttempts}, registered=${ready.registered || Boolean(client.registered)}, socket=${client.socket?.readyState})`);
|
|
300
|
+
return;
|
|
301
|
+
} catch (err) {
|
|
302
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
303
|
+
logger.warn(`⚠️ Stream 建立失败 attempt=${attempt}/${maxAttempts}: ${lastError.message}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
throw new Error(`钉钉 Stream 在 ${maxAttempts} 次尝试后仍无法 OPEN。 最后错误: ${lastError?.message || "unknown"}`);
|
|
307
|
+
}
|
|
308
|
+
/** 统一重连函数,带指数退避(无限重连) */
|
|
309
|
+
async function doReconnect(immediate = false) {
|
|
310
|
+
if (isReconnecting || isStopped) {
|
|
311
|
+
logger.debug(`正在重连中或已停止,跳过`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
isReconnecting = true;
|
|
315
|
+
if (!immediate && reconnectAttempts > 0) {
|
|
316
|
+
const delay = calculateBackoffDelay(reconnectAttempts);
|
|
317
|
+
logger.info(`⏳ 等待 ${Math.round(delay / 1e3)} 秒后重连 (尝试 ${reconnectAttempts + 1})`);
|
|
318
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
if (client.socket?.readyState === 1 || client.socket?.readyState === 3) {
|
|
322
|
+
await client.disconnect();
|
|
323
|
+
logger.info(`已断开旧连接`);
|
|
324
|
+
}
|
|
325
|
+
await ensureRegisteredConnection(REGISTERED_CONNECT_MAX_ATTEMPTS);
|
|
326
|
+
noteSocketAlive("reconnect-ok");
|
|
327
|
+
connectionEstablishedTime = Date.now();
|
|
328
|
+
reconnectAttempts = 0;
|
|
329
|
+
lastHardReconnectAt = Date.now();
|
|
330
|
+
onStatusChange?.({
|
|
331
|
+
connected: true,
|
|
332
|
+
lastConnectedAt: Date.now()
|
|
333
|
+
});
|
|
334
|
+
logger.info(`✅ 重连成功 (socket=${client.socket?.readyState}, registered=${Boolean(client.registered)})`);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
reconnectAttempts++;
|
|
337
|
+
onStatusChange?.({ connected: false });
|
|
338
|
+
logger.error(`重连失败:${err.message} (尝试 ${reconnectAttempts})`);
|
|
339
|
+
throw err;
|
|
340
|
+
} finally {
|
|
341
|
+
isReconnecting = false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/** 监听 pong 响应(更新 socket 可用时间) */
|
|
345
|
+
function setupPongListener() {
|
|
346
|
+
client.socket?.on("pong", () => {
|
|
347
|
+
lastSocketAvailableTime = Date.now();
|
|
348
|
+
consecutiveStaleMisses = 0;
|
|
349
|
+
logger.debug(`收到 PONG 响应`);
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
function noteSocketAlive(reason) {
|
|
353
|
+
lastSocketAvailableTime = Date.now();
|
|
354
|
+
consecutiveStaleMisses = 0;
|
|
355
|
+
logger.debug(`socket alive reason=${reason}`);
|
|
356
|
+
}
|
|
357
|
+
/** 监听 WebSocket message 事件,收到 disconnect 消息时立即触发重连 */
|
|
358
|
+
function setupMessageListener() {
|
|
359
|
+
client.socket?.on("message", (data) => {
|
|
360
|
+
try {
|
|
361
|
+
const msg = JSON.parse(data);
|
|
362
|
+
if (msg.type === "SYSTEM" && msg.headers?.topic === "disconnect") {
|
|
363
|
+
if (!isStopped && !isReconnecting) doReconnect(true).catch((err) => {
|
|
364
|
+
logger.error(`[${accountId}] 重连失败:${err.message}`);
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
} catch (e) {}
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
/** 监听 WebSocket close 事件,服务端主动断开时立即触发重连 */
|
|
371
|
+
function setupCloseListener() {
|
|
372
|
+
client.socket?.on("close", (code, reason) => {
|
|
373
|
+
logger.info(`WebSocket close: code=${code}, reason=${reason || "未知"}, isStopped=${isStopped}`);
|
|
374
|
+
onStatusChange?.({ connected: false });
|
|
375
|
+
if (isStopped) return;
|
|
376
|
+
setTimeout(() => {
|
|
377
|
+
doReconnect(true).catch((err) => {
|
|
378
|
+
logger.error(`重连失败:${err.message}`);
|
|
379
|
+
});
|
|
380
|
+
}, 0);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* 请求硬重连(拆 socket)。
|
|
385
|
+
* - socket 仍 OPEN:先补 PING,连续 misses 未达阈值则绝不拆连接(防空窗吞消息)
|
|
386
|
+
* - socket 已死:立即重连
|
|
387
|
+
* - 冷却:非死连接时距上次硬重连 < MIN_RECONNECT_GAP_MS 则跳过
|
|
388
|
+
*/
|
|
389
|
+
async function requestHardReconnect(reason, socketDead = false) {
|
|
390
|
+
if (isReconnecting || isStopped) return;
|
|
391
|
+
const socketState = client.socket?.readyState;
|
|
392
|
+
if (!socketDead && socketState === 1) {
|
|
393
|
+
try {
|
|
394
|
+
client.socket?.ping();
|
|
395
|
+
logger.warn(`⚠️ 软超时但 socket 仍 OPEN (reason=${reason}, misses=${consecutiveStaleMisses}/${HARD_RECONNECT_AFTER_MISSES}),仅补 PING`);
|
|
396
|
+
} catch (err) {
|
|
397
|
+
logger.warn(`补 PING 失败: ${err.message}`);
|
|
398
|
+
consecutiveStaleMisses += 1;
|
|
399
|
+
}
|
|
400
|
+
if (consecutiveStaleMisses < HARD_RECONNECT_AFTER_MISSES) return;
|
|
401
|
+
}
|
|
402
|
+
if (!socketDead) {
|
|
403
|
+
const gap = Date.now() - lastHardReconnectAt;
|
|
404
|
+
if (lastHardReconnectAt > 0 && gap < MIN_RECONNECT_GAP_MS) {
|
|
405
|
+
logger.warn(`⚠️ 跳过硬重连:距上次仅 ${Math.round(gap / 1e3)}s < ${MIN_RECONNECT_GAP_MS / 1e3}s 冷却 (reason=${reason})`);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
lastHardReconnectAt = Date.now();
|
|
410
|
+
consecutiveStaleMisses = 0;
|
|
411
|
+
logger.info(`🔄 硬重连 reason=${reason} socket=${socketState}`);
|
|
412
|
+
await doReconnect(socketDead);
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* 启动 keepAlive:
|
|
416
|
+
* - 10s 发 ping;pong 刷新 lastSocketAvailableTime
|
|
417
|
+
* - 软超时只计数 + 补 ping,达到 HARD_RECONNECT_AFTER_MISSES 且 socket 异常才拆连接
|
|
418
|
+
* - 处理中任务期间绝不硬重连
|
|
419
|
+
*/
|
|
420
|
+
function startKeepAlive() {
|
|
421
|
+
logger.debug(`🚀 启动 keepAlive 定时器,间隔=${HEARTBEAT_INTERVAL / 1e3}秒`);
|
|
422
|
+
keepAliveTimer = setInterval(async () => {
|
|
423
|
+
if (isStopped) {
|
|
424
|
+
if (keepAliveTimer) clearInterval(keepAliveTimer);
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
const elapsed = Date.now() - lastSocketAvailableTime;
|
|
429
|
+
const socketState = client.socket?.readyState;
|
|
430
|
+
const timeSinceConnection = Date.now() - connectionEstablishedTime;
|
|
431
|
+
logger.debug(`心跳检测:socket=${socketState}, elapsed=${Math.round(elapsed / 1e3)}s, misses=${consecutiveStaleMisses}, processing=${activeMessageProcessing}, connectedFor=${Math.round(timeSinceConnection / 1e3)}s`);
|
|
432
|
+
if (socketState !== 1) {
|
|
433
|
+
if (timeSinceConnection < 15e3) {
|
|
434
|
+
logger.debug(`⏳ 连接建立中(已 ${Math.round(timeSinceConnection / 1e3)}s),跳过状态检查`);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (activeMessageProcessing) {
|
|
438
|
+
logger.warn(`⚠️ socket 非 OPEN 但消息处理中,暂缓硬重连 state=${socketState}`);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
await requestHardReconnect(`socket-dead state=${socketState}`, true);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (elapsed > SOFT_STALE_MS) {
|
|
445
|
+
consecutiveStaleMisses += 1;
|
|
446
|
+
if (activeMessageProcessing) {
|
|
447
|
+
noteSocketAlive("processing-hold");
|
|
448
|
+
logger.warn(`⚠️ 软超时但处理中,刷新活跃时间不重连 (elapsed=${Math.round(elapsed / 1e3)}s)`);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
logger.warn(`⚠️ 软超时 misses=${consecutiveStaleMisses}/${HARD_RECONNECT_AFTER_MISSES} elapsed=${Math.round(elapsed / 1e3)}s socket=OPEN`);
|
|
452
|
+
await requestHardReconnect("stale-pong", false);
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
consecutiveStaleMisses = 0;
|
|
456
|
+
try {
|
|
457
|
+
client.socket?.ping();
|
|
458
|
+
logger.debug(`💓 发送 PING 心跳成功`);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
logger.warn(`发送 PING 失败:${err.message}`);
|
|
461
|
+
consecutiveStaleMisses += 1;
|
|
462
|
+
if (consecutiveStaleMisses >= HARD_RECONNECT_AFTER_MISSES) await requestHardReconnect("ping-failed", true);
|
|
463
|
+
}
|
|
464
|
+
} catch (err) {
|
|
465
|
+
logger.error(`keepAlive 检测失败:${err.message}`);
|
|
466
|
+
}
|
|
467
|
+
}, HEARTBEAT_INTERVAL);
|
|
468
|
+
logger.debug(`✅ keepAlive 定时器已启动`);
|
|
469
|
+
return () => {
|
|
470
|
+
if (keepAliveTimer) clearInterval(keepAliveTimer);
|
|
471
|
+
keepAliveTimer = null;
|
|
472
|
+
logger.debug(`keepAlive 定时器已清理`);
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
/** 停止并清理所有资源 */
|
|
476
|
+
function stop() {
|
|
477
|
+
isStopped = true;
|
|
478
|
+
if (keepAliveTimer) clearInterval(keepAliveTimer);
|
|
479
|
+
keepAliveTimer = null;
|
|
480
|
+
clearProcessingKeepAliveTimer();
|
|
481
|
+
wsCallbackActive = false;
|
|
482
|
+
activeMessageProcessing = false;
|
|
483
|
+
unsubscribeBackgroundWork?.();
|
|
484
|
+
unsubscribeBackgroundWork = null;
|
|
485
|
+
if (client.socket) client.socket.removeAllListeners();
|
|
486
|
+
logger.debug(`Connection 已停止`);
|
|
487
|
+
}
|
|
488
|
+
return new Promise(async (resolve, reject) => {
|
|
489
|
+
if (abortSignal) {
|
|
490
|
+
const onAbort = async () => {
|
|
491
|
+
logger.info(`Abort signal received, stopping...`);
|
|
492
|
+
stop();
|
|
493
|
+
try {
|
|
494
|
+
if (client.socket && client.socket.readyState === 1) await client.disconnect();
|
|
495
|
+
} catch (err) {
|
|
496
|
+
logger.warn(`断开连接时出错:${err.message}`);
|
|
497
|
+
}
|
|
498
|
+
resolve();
|
|
499
|
+
};
|
|
500
|
+
abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
501
|
+
}
|
|
502
|
+
let receivedCount = 0;
|
|
503
|
+
let processedCount = 0;
|
|
504
|
+
let lastMessageTime = Date.now();
|
|
505
|
+
const statsInterval = setInterval(() => {
|
|
506
|
+
const timeSinceLastMessage = Math.round((Date.now() - lastMessageTime) / 1e3);
|
|
507
|
+
logger.info(`统计:收到=${receivedCount}, 处理=${processedCount}, 丢失=${receivedCount - processedCount}, 距上次消息=${timeSinceLastMessage}s`);
|
|
508
|
+
}, 6e4);
|
|
509
|
+
client.registerCallbackListener(TOPIC_ROBOT, async (res) => {
|
|
510
|
+
receivedCount++;
|
|
511
|
+
lastMessageTime = Date.now();
|
|
512
|
+
noteSocketAlive("inbound-callback");
|
|
513
|
+
if (client.registered !== true) {
|
|
514
|
+
client.registered = true;
|
|
515
|
+
logger.info(`✅ 收到机器人 CALLBACK,视为订阅已生效(此前可能未观察到 SYSTEM/REGISTERED)`);
|
|
516
|
+
}
|
|
517
|
+
onStatusChange?.({ lastInboundAt: Date.now() });
|
|
518
|
+
const messageId = res.headers?.messageId;
|
|
519
|
+
let acked = false;
|
|
520
|
+
const ackMessage = (why) => {
|
|
521
|
+
if (acked || !messageId) return;
|
|
522
|
+
try {
|
|
523
|
+
client.socketCallBackResponse(messageId, { success: true });
|
|
524
|
+
acked = true;
|
|
525
|
+
logger.debug(`ACK messageId=${messageId} (${why})`);
|
|
526
|
+
} catch (ackErr) {
|
|
527
|
+
logger.warn(`确认回调失败: ${ackErr?.message || ackErr}`);
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
markMessageProcessingStart();
|
|
531
|
+
try {
|
|
532
|
+
let data;
|
|
533
|
+
try {
|
|
534
|
+
data = JSON.parse(res.data);
|
|
535
|
+
} catch (parseError) {
|
|
536
|
+
logger.error(`JSON 解析失败: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
537
|
+
throw new Error(`Invalid JSON from DingTalk: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
|
|
538
|
+
}
|
|
539
|
+
const businessMsgId = data.msgId;
|
|
540
|
+
const convType = data.conversationType === "1" ? "DM" : data.conversationType === "2" ? "Group" : String(data.conversationType ?? "?");
|
|
541
|
+
const preview = (data.text?.content || "").trim().slice(0, 80) || data.msgtype || "(no text)";
|
|
542
|
+
logger.info(`📩 入站 ${convType} from=${data.senderNick || data.senderStaffId || data.senderId || "?"} msg=${businessMsgId || messageId || "-"} preview=${JSON.stringify(preview)}`);
|
|
543
|
+
logger.debug(`Headers: ${JSON.stringify(res.headers || {})}`);
|
|
544
|
+
logger.debug(`字段: ${Object.keys(data).join(",")}`);
|
|
545
|
+
if (data.chatbotUserId || data.chatbotCorpId) {
|
|
546
|
+
const key = `${accountId}:${data.chatbotUserId || ""}:${data.chatbotCorpId || ""}`;
|
|
547
|
+
if (!globalThis.__dingtalkBotIdentityLogged) globalThis.__dingtalkBotIdentityLogged = /* @__PURE__ */ new Set();
|
|
548
|
+
const seen = globalThis.__dingtalkBotIdentityLogged;
|
|
549
|
+
if (!seen.has(key)) {
|
|
550
|
+
seen.add(key);
|
|
551
|
+
console.log(`[DingTalk:${accountId}] [BotIdentity] chatbotUserId=${data.chatbotUserId || "N/A"} chatbotCorpId=${data.chatbotCorpId || "N/A"}`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (checkAndMarkDingtalkMessage(accountId, messageId, businessMsgId)) {
|
|
555
|
+
processedCount++;
|
|
556
|
+
ackMessage("duplicate");
|
|
557
|
+
logger.warn(`⚠️ 重复消息已跳过 protocol=${messageId || "-"} business=${businessMsgId || "-"}`);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
await messageHandler({
|
|
561
|
+
accountId,
|
|
562
|
+
config: account.config,
|
|
563
|
+
data,
|
|
564
|
+
sessionWebhook: data.sessionWebhook,
|
|
565
|
+
runtime,
|
|
566
|
+
log,
|
|
567
|
+
cfg: clawdbotConfig
|
|
568
|
+
});
|
|
569
|
+
ackMessage("enqueued");
|
|
570
|
+
noteSocketAlive("after-enqueue");
|
|
571
|
+
processedCount++;
|
|
572
|
+
logger.info(`✅ 入队完成 (${processedCount}/${receivedCount})`);
|
|
573
|
+
} catch (error) {
|
|
574
|
+
processedCount++;
|
|
575
|
+
logger.error(`❌ 入站处理失败 (${processedCount}/${receivedCount}): ${error?.message || "未知错误"}`);
|
|
576
|
+
if (error?.stack) logger.debug(error.stack);
|
|
577
|
+
if (!acked) logger.warn(`⚠️ 未 ACK messageId=${messageId || "N/A"},等待钉钉重投`);
|
|
578
|
+
} finally {
|
|
579
|
+
markMessageProcessingEnd();
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
const cleanup = () => {
|
|
583
|
+
clearInterval(statsInterval);
|
|
584
|
+
stop();
|
|
585
|
+
};
|
|
586
|
+
try {
|
|
587
|
+
await ensureRegisteredConnection(REGISTERED_CONNECT_MAX_ATTEMPTS);
|
|
588
|
+
noteSocketAlive("initial-ready");
|
|
589
|
+
connectionEstablishedTime = Date.now();
|
|
590
|
+
logger.info(`Connected to DingTalk Stream successfully`);
|
|
591
|
+
logger.info(`PID: ${process.pid}`);
|
|
592
|
+
logger.info(`✅ keepAlive: 心跳 ${HEARTBEAT_INTERVAL / 1e3}s / 软超时 ${SOFT_STALE_MS / 1e3}s×${HARD_RECONNECT_AFTER_MISSES} / 处理中刷新 ${MESSAGE_PROCESSING_KEEPALIVE_MS / 1e3}s / 重连冷却 ${MIN_RECONNECT_GAP_MS / 1e3}s, registered=${Boolean(client.registered)}`);
|
|
593
|
+
printConnectionNoticeOnce();
|
|
594
|
+
onStatusChange?.({
|
|
595
|
+
connected: true,
|
|
596
|
+
lastConnectedAt: Date.now()
|
|
597
|
+
});
|
|
598
|
+
const cleanupKeepAlive = startKeepAlive();
|
|
599
|
+
const enhancedCleanup = () => {
|
|
600
|
+
cleanupKeepAlive();
|
|
601
|
+
clearInterval(statsInterval);
|
|
602
|
+
stop();
|
|
603
|
+
};
|
|
604
|
+
process.once("exit", enhancedCleanup);
|
|
605
|
+
process.once("SIGINT", enhancedCleanup);
|
|
606
|
+
process.once("SIGTERM", enhancedCleanup);
|
|
607
|
+
} catch (error) {
|
|
608
|
+
cleanup();
|
|
609
|
+
logger.info(`连接失败,错误详情:`);
|
|
610
|
+
logger.info(` - error.message: ${error.message}`);
|
|
611
|
+
logger.info(` - error.response?.status: ${error.response?.status}`);
|
|
612
|
+
logger.info(` - error.response?.data: ${JSON.stringify(error.response?.data || {})}`);
|
|
613
|
+
logger.info(` - error.code: ${error.code}`);
|
|
614
|
+
logger.info(` - error.stack: ${error.stack?.split("\n").slice(0, 3).join("\n")}`);
|
|
615
|
+
if (error.response?.status === 400 || error.message?.includes("status code 400") || error.message?.includes("400")) {
|
|
616
|
+
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 || {})}`));
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (error.response?.status === 401 || error.message?.includes("401")) {
|
|
620
|
+
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}`));
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
reject(/* @__PURE__ */ new Error(`[DingTalk][${accountId}] Failed to connect to DingTalk Stream: ${error.message}`));
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
client.on("error", (err) => {
|
|
627
|
+
logger.error(`Connection error: ${err.message}`);
|
|
628
|
+
});
|
|
629
|
+
client.on("reconnect", () => {
|
|
630
|
+
logger.info(`SDK reconnecting...`);
|
|
631
|
+
});
|
|
632
|
+
client.on("reconnected", () => {
|
|
633
|
+
logger.info(`✅ SDK reconnected successfully`);
|
|
634
|
+
});
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
function resolveReactionSyntheticEvent(event) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
//#endregion
|
|
641
|
+
export { monitorSingleAccount, resolveReactionSyntheticEvent };
|
package/dist/entry-bundled.mjs
CHANGED
|
@@ -23,7 +23,7 @@ var entry_bundled_default = defineBundledChannelEntry({
|
|
|
23
23
|
exportName: "setDingtalkRuntime"
|
|
24
24
|
},
|
|
25
25
|
async registerFull(api) {
|
|
26
|
-
const { registerGatewayMethods } = await import("./gateway-methods-
|
|
26
|
+
const { registerGatewayMethods } = await import("./gateway-methods-DQ6TwOAr.mjs");
|
|
27
27
|
registerGatewayMethods(api);
|
|
28
28
|
}
|
|
29
29
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as resolveDingtalkAccount, t as listDingtalkAccountIds } from "./accounts-BAzdqkAV.mjs";
|
|
2
2
|
import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
3
3
|
import { r as getAccessToken, t as DINGTALK_API } from "./utils-CIfI_3Jh.mjs";
|
|
4
|
-
import { c as prepareMultiBotMentions, d as finishAICard, i as sendProactive, s as buildBotMentionTable } from "./messaging-
|
|
4
|
+
import { c as prepareMultiBotMentions, d as finishAICard, i as sendProactive, s as buildBotMentionTable } from "./messaging-CK0Wz3Ze.mjs";
|
|
5
5
|
import { d as getUnionId, h as recallEmotionReply } from "./utils-legacy-DQ3HeGHh.mjs";
|
|
6
6
|
//#region src/docs.ts
|
|
7
7
|
var DingtalkDocsClient = class {
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-
|
|
2
|
-
import { t as registerGatewayMethods } from "./gateway-methods
|
|
1
|
+
import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-DbqGRyhI.mjs";
|
|
2
|
+
import { t as registerGatewayMethods } from "./gateway-methods-BdcpfxEZ.mjs";
|
|
3
3
|
//#region index.ts
|
|
4
4
|
/**
|
|
5
5
|
* 检测同一 plugin id 在多个路径被加载的情况。
|