@dingtalk-real-ai/dingtalk-connector 0.8.20-beta.8 → 0.8.20

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
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.8.20] - 2026-04-28
9
+
10
+ ### 修复 / Fixes
11
+ - 🐛 **OpenClaw 插件加载兼容性 (Issue #527)** — `configSchema` 改为延迟初始化,通过 `createRequire` 解析 `openclaw/plugin-sdk/core`,修复插件安装到 `~/.openclaw/extensions/` 时 ESM 裸说明符解析失败导致的 "Cannot find package 'openclaw'" 崩溃
12
+ **OpenClaw plugin load compatibility (Issue #527)** — `configSchema` deferred to lazy init via `createRequire`, fixing "Cannot find package 'openclaw'" crash when plugin is installed to `~/.openclaw/extensions/`
13
+
14
+ - 🐛 **Onboarding 动态导入** — `promptSingleChannelSecretInput` 从静态 import 改为动态 `import()`,避免在 ESM 加载阶段触发同样的裸说明符解析错误
15
+ **Onboarding dynamic import** — `promptSingleChannelSecretInput` switched from static to dynamic `import()` to avoid bare specifier resolution error during ESM loading
16
+
17
+ ### 改进 / Improvements
18
+ - ✅ **DWS CLI 版本管理重构** — `ensureDwsCli()` 新增 `compareVersions()` 语义版本比较,支持四种场景:目标版本更高时自动升级、本地版本更高时询问是否覆盖、版本一致时跳过、全新安装时显示已安装版本号
19
+ **DWS CLI version management refactor** — `ensureDwsCli()` now uses `compareVersions()` for semver comparison with four scenarios: auto-upgrade when target is newer, prompt before downgrade, skip when equal, show version on fresh install
20
+
8
21
  ## [0.8.19] - 2026-04-25
9
22
 
10
23
  ### 新增 / Added
@@ -428,6 +428,22 @@ function getTargetDwsVersion() {
428
428
  return versionMatch ? versionMatch[1] : null;
429
429
  }
430
430
 
431
+ /**
432
+ * Compare two semver version strings.
433
+ * Returns: positive if a > b, negative if a < b, 0 if equal.
434
+ */
435
+ function compareVersions(versionA, versionB) {
436
+ const partsA = versionA.split('.').map(Number);
437
+ const partsB = versionB.split('.').map(Number);
438
+ const maxLength = Math.max(partsA.length, partsB.length);
439
+ for (let i = 0; i < maxLength; i++) {
440
+ const partA = partsA[i] || 0;
441
+ const partB = partsB[i] || 0;
442
+ if (partA !== partB) return partA - partB;
443
+ }
444
+ return 0;
445
+ }
446
+
431
447
  function askUserConfirmation(question) {
432
448
  const { createInterface } = createRequire(import.meta.url)('node:readline');
433
449
  const rl = createInterface({
@@ -498,33 +514,50 @@ function isDwsAuthenticated() {
498
514
  }
499
515
 
500
516
  async function ensureDwsCli() {
517
+ const targetVersion = getTargetDwsVersion();
518
+
501
519
  if (isDwsInstalled()) {
502
520
  const installedVersion = getInstalledDwsVersion();
503
- const targetVersion = getTargetDwsVersion();
504
521
  const versionDisplay = installedVersion ? `v${installedVersion}` : 'unknown version';
505
-
506
- console.log(dim(` ✔ dws CLI already installed (${versionDisplay})`) + '\n');
507
-
508
- // Check if a newer version is available
509
- if (installedVersion && targetVersion && installedVersion !== targetVersion) {
510
- console.log(orange(` ℹ A newer version of dws CLI is available: v${targetVersion} (current: v${installedVersion})`) + '\n');
522
+ const comparison = (installedVersion && targetVersion) ? compareVersions(targetVersion, installedVersion) : 0;
523
+
524
+ if (comparison > 0) {
525
+ // Scenario 1: target > local upgrade directly
526
+ console.log(dim(` ℹ dws CLI detected (${versionDisplay}), upgrading to v${targetVersion}...`) + '\n');
527
+ console.log(dim(` v${installedVersion} v${targetVersion}`) + '\n');
528
+ const upgraded = installDwsCli();
529
+ if (upgraded) {
530
+ const newVersion = getInstalledDwsVersion();
531
+ console.log(green(` ✔ dws CLI upgraded to v${newVersion || targetVersion}`) + '\n');
532
+ } else {
533
+ console.log(red(' ⚠ Upgrade failed. Continuing with current version.') + '\n');
534
+ }
535
+ } else if (comparison < 0) {
536
+ // Scenario 2: target < local → ask user before downgrading
537
+ console.log(dim(` ℹ dws CLI detected (${versionDisplay})`) + '\n');
538
+ console.log(orange(` ⚠ Your local dws CLI (v${installedVersion}) is newer than the bundled version (v${targetVersion}).`) + '\n');
539
+ console.log(dim(` Overwriting would downgrade: v${installedVersion} → v${targetVersion}`) + '\n');
511
540
  const answer = await askUserConfirmation(
512
- ` Do you want to upgrade dws CLI to v${targetVersion}? (覆盖安装新版本?) [y/N] `
541
+ ` Do you want to overwrite with v${targetVersion}? (是否覆盖为旧版本?) [y/N] `
513
542
  );
514
543
  if (answer === 'y' || answer === 'yes') {
515
544
  console.log('');
516
- const upgraded = installDwsCli();
517
- if (upgraded) {
545
+ const downgraded = installDwsCli();
546
+ if (downgraded) {
518
547
  const newVersion = getInstalledDwsVersion();
519
- console.log(green(` ✔ dws CLI upgraded to v${newVersion || targetVersion}`) + '\n');
548
+ console.log(green(` ✔ dws CLI replaced with v${newVersion || targetVersion}`) + '\n');
520
549
  } else {
521
- console.log(red(' ⚠ Upgrade failed. Continuing with current version.') + '\n');
550
+ console.log(red(' ⚠ Overwrite failed. Continuing with current version.') + '\n');
522
551
  }
523
552
  } else {
524
553
  console.log('\n' + dim(` Keeping current dws CLI v${installedVersion}`) + '\n');
525
554
  }
555
+ } else {
556
+ // Scenario 3: versions are equal → skip
557
+ console.log(dim(` ✔ dws CLI already installed (${versionDisplay}), version is up to date`) + '\n');
526
558
  }
527
559
 
560
+ // Check authentication status regardless of version scenario
528
561
  if (isDwsAuthenticated()) {
529
562
  console.log(dim(' ✔ dws CLI authenticated') + '\n');
530
563
  } else {
@@ -534,6 +567,7 @@ async function ensureDwsCli() {
534
567
  return;
535
568
  }
536
569
 
570
+ // Scenario 4: dws not installed → install and show version
537
571
  const installed = installDwsCli();
538
572
  if (!installed) {
539
573
  console.log(red(' ⚠ Could not install dws CLI automatically.') + '\n');
@@ -544,7 +578,10 @@ async function ensureDwsCli() {
544
578
  return;
545
579
  }
546
580
 
547
- console.log(dim(' ℹ dws CLI installed. Authorization will be triggered when Agent uses dws features.') + '\n');
581
+ const freshVersion = getInstalledDwsVersion();
582
+ const freshDisplay = freshVersion ? `v${freshVersion}` : (targetVersion ? `v${targetVersion}` : '');
583
+ console.log(green(` ✔ dws CLI installed (${freshDisplay})`) + '\n');
584
+ console.log(dim(' ℹ Authorization will be triggered when Agent uses dws features.') + '\n');
548
585
  console.log(dim(' You can also authorize manually anytime: ') + cyan('dws auth login') + '\n');
549
586
  }
550
587
 
@@ -24,7 +24,6 @@ function toLocalPath(raw) {
24
24
  if (filePath.startsWith("file://")) filePath = filePath.replace("file://", "");
25
25
  else if (filePath.startsWith("MEDIA:")) filePath = filePath.replace("MEDIA:", "");
26
26
  else if (filePath.startsWith("attachment://")) filePath = filePath.replace("attachment://", "");
27
- filePath = filePath.trim();
28
27
  try {
29
28
  filePath = decodeURIComponent(filePath);
30
29
  } catch {}
@@ -1,2 +1,2 @@
1
- import { a as VIDEO_MARKER_PATTERN, r as FILE_MARKER_PATTERN, t as AUDIO_MARKER_PATTERN } from "./common-PeRcixHR.mjs";
1
+ import { a as VIDEO_MARKER_PATTERN, r as FILE_MARKER_PATTERN, t as AUDIO_MARKER_PATTERN } from "./common-BGJlWkEp.mjs";
2
2
  export { AUDIO_MARKER_PATTERN, FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN };
@@ -6,7 +6,7 @@ import * as fs from "fs";
6
6
  *
7
7
  * 职责:
8
8
  * - 管理单个钉钉账号的 WebSocket 连接
9
- * - 实现应用层心跳检测(10 秒间隔 ping,90 秒无 pong 响应则重连)
9
+ * - 实现应用层心跳检测(10 秒间隔,90 秒超时)
10
10
  * - 处理连接重连逻辑,带指数退避
11
11
  * - 消息去重(内置 Map,5 分钟 TTL)
12
12
  *
@@ -18,7 +18,7 @@ import * as fs from "fs";
18
18
  /** 心跳间隔(毫秒) */
19
19
  const HEARTBEAT_INTERVAL = 10 * 1e3;
20
20
  /** 超时阈值(毫秒) */
21
- const TIMEOUT_THRESHOLD = 90 * 1e3;
21
+ const TIMEOUT_THRESHOLD = 20 * 1e3;
22
22
  /** 基础退避时间(毫秒) */
23
23
  const BASE_BACKOFF_DELAY = 1e3;
24
24
  /** 最大退避时间(毫秒) */
@@ -30,11 +30,6 @@ async function monitorSingleAccount(opts) {
30
30
  const log = runtime?.log;
31
31
  const { createLoggerFromConfig } = await import("./logger-BeHWErmX.mjs");
32
32
  const logger = createLoggerFromConfig(account.config, `DingTalk:${accountId}`);
33
- const resolvedConfig = {
34
- ...account.config,
35
- ...account.clientId != null ? { clientId: account.clientId } : {},
36
- ...account.clientSecret != null ? { clientSecret: account.clientSecret } : {}
37
- };
38
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.`);
39
34
  const clientIdStr = String(account.clientId);
40
35
  const clientSecretStr = String(account.clientSecret);
@@ -156,9 +151,6 @@ async function monitorSingleAccount(opts) {
156
151
  client.socket?.once("open", onOpen);
157
152
  client.socket?.once("error", onError);
158
153
  })) throw new Error(`连接建立超时或失败`);
159
- setupPongListener();
160
- setupMessageListener();
161
- setupCloseListener();
162
154
  lastSocketAvailableTime = Date.now();
163
155
  connectionEstablishedTime = Date.now();
164
156
  reconnectAttempts = 0;
@@ -271,6 +263,9 @@ async function monitorSingleAccount(opts) {
271
263
  if (client.socket) client.socket.removeAllListeners();
272
264
  logger.debug(`Connection 已停止`);
273
265
  }
266
+ setupPongListener();
267
+ setupMessageListener();
268
+ setupCloseListener();
274
269
  return new Promise(async (resolve, reject) => {
275
270
  if (abortSignal) {
276
271
  const onAbort = async () => {
@@ -345,7 +340,7 @@ async function monitorSingleAccount(opts) {
345
340
  logger.info(`🚀 开始处理消息...`);
346
341
  await messageHandler({
347
342
  accountId,
348
- config: resolvedConfig,
343
+ config: account.config,
349
344
  data,
350
345
  sessionWebhook: data.sessionWebhook,
351
346
  runtime,
@@ -374,10 +369,7 @@ async function monitorSingleAccount(opts) {
374
369
  await client.connect();
375
370
  logger.info(`Connected to DingTalk Stream successfully`);
376
371
  logger.info(`PID: ${process.pid}`);
377
- logger.info(`✅ 自定义 keepAlive: true (${HEARTBEAT_INTERVAL / 1e3}秒心跳,${TIMEOUT_THRESHOLD / 1e3}秒超时), 指数退避重连`);
378
- setupPongListener();
379
- setupMessageListener();
380
- setupCloseListener();
372
+ logger.info(`✅ 自定义 keepAlive: true (10 秒心跳,90 秒超时), 指数退避重连`);
381
373
  onStatusChange?.({
382
374
  connected: true,
383
375
  lastConnectedAt: Date.now()
@@ -407,14 +399,6 @@ async function monitorSingleAccount(opts) {
407
399
  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}`));
408
400
  return;
409
401
  }
410
- if (error.response?.status === 403 || error.message?.includes("status code 403") || error.message?.includes("403")) {
411
- reject(/* @__PURE__ */ new Error(`[DingTalk][${accountId}] Forbidden (403):\n - 可能原因 / Possible causes:\n 1. 机器人未开启 Stream 模式(需在钉钉开放平台 → 消息接收模式中设置)\n 2. 机器人处于"开发中"状态,尚未发布上线\n 3. API 调用频率超限(QPS Limit)\n - Error details: ${error.message}\n - Response data: ${JSON.stringify(error.response?.data || {})}`));
412
- return;
413
- }
414
- if (error.message?.includes("protocol") || error.message?.includes("mismatch") || error.message?.includes("upgrade")) {
415
- reject(/* @__PURE__ */ new Error(`[DingTalk][${accountId}] Failed to connect to DingTalk Stream: ${error.message}\n - 可能原因 / Possible causes:\n 1. dingtalk-stream SDK 版本过旧,请升级插件: npx openclaw@latest add @dingtalk-real-ai/dingtalk-connector\n 2. 网络代理或防火墙拦截了 WebSocket 连接\n 3. 钉钉服务端协议已更新,当前版本不兼容\n - 建议 / Suggestions:\n 1. 升级到最新版本的 dingtalk-connector 插件\n 2. 检查网络环境是否允许 WebSocket 连接\n 3. 如仍有问题,请提 Issue 并附上完整日志`));
416
- return;
417
- }
418
402
  reject(/* @__PURE__ */ new Error(`[DingTalk][${accountId}] Failed to connect to DingTalk Stream: ${error.message}`));
419
403
  return;
420
404
  }
@@ -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-DVDpyryu.mjs");
26
+ const { registerGatewayMethods } = await import("./gateway-methods-DtdiDpYK.mjs");
27
27
  registerGatewayMethods(api);
28
28
  }
29
29
  });
@@ -1,7 +1,7 @@
1
1
  import { a as resolveDingtalkAccount, t as listDingtalkAccountIds } from "./accounts-CF4oK_HZ.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, i as sendProactive, s as buildBotMentionTable, u as finishAICard } from "./messaging-BNycOrEX.mjs";
4
+ import { c as prepareMultiBotMentions, i as sendProactive, s as buildBotMentionTable, u as finishAICard } from "./messaging-CyIJY4h2.mjs";
5
5
  import { c as getUnionId, d as recallEmotionReply } from "./utils-legacy-CALCPP1t.mjs";
6
6
  //#region src/docs.ts
7
7
  var DingtalkDocsClient = class {
@@ -0,0 +1,2 @@
1
+ import { t as registerGatewayMethods } from "./gateway-methods-DI8lkjSd.mjs";
2
+ export { registerGatewayMethods };
package/dist/index.d.mts CHANGED
@@ -156,6 +156,19 @@ type ResolvedDingtalkAccount = {
156
156
  //#endregion
157
157
  //#region src/channel.d.ts
158
158
  declare const dingtalkPlugin: ChannelPlugin<ResolvedDingtalkAccount>;
159
+ /**
160
+ * Synchronously initializes `dingtalkPlugin.configSchema` using `createRequire`.
161
+ *
162
+ * Static `import ... from "openclaw/plugin-sdk/core"` causes
163
+ * "Cannot find package 'openclaw'" when the plugin is installed to
164
+ * `~/.openclaw/extensions/` (Issue #527) because the ESM loader resolves
165
+ * bare specifiers at parse time before the gateway's jiti alias map is active.
166
+ *
167
+ * By deferring the resolve to `register()` time and using `createRequire`
168
+ * (which searches the gateway's own `node_modules`), we avoid the crash
169
+ * while keeping the call synchronous as required by the plugin API.
170
+ */
171
+ declare function initDingtalkPluginConfigSchema(): void;
159
172
  //#endregion
160
173
  //#region src/runtime.d.ts
161
174
  declare const setDingtalkRuntime: (next: PluginRuntime) => void, getDingtalkRuntime: () => PluginRuntime;
@@ -169,4 +182,4 @@ declare function registerGatewayMethods(api: OpenClawPluginApi): void;
169
182
  //#region index.d.ts
170
183
  declare function register(api: OpenClawPluginApi): void;
171
184
  //#endregion
172
- export { register as default, dingtalkPlugin, registerGatewayMethods, setDingtalkRuntime };
185
+ export { register as default, dingtalkPlugin, initDingtalkPluginConfigSchema, registerGatewayMethods, setDingtalkRuntime };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-CJCdIYOh.mjs";
2
- import { t as registerGatewayMethods } from "./gateway-methods-BZSmkOsG.mjs";
1
+ import { a as initDingtalkPluginConfigSchema, i as dingtalkPlugin, n as setDingtalkRuntime } from "./runtime-b4xvqwW6.mjs";
2
+ import { t as registerGatewayMethods } from "./gateway-methods-DI8lkjSd.mjs";
3
3
  //#region index.ts
4
4
  /**
5
5
  * 检测同一 plugin id 在多个路径被加载的情况。
@@ -37,8 +37,9 @@ function recordAndCheckLoadPath(api) {
37
37
  function register(api) {
38
38
  recordAndCheckLoadPath(api);
39
39
  setDingtalkRuntime(api.runtime);
40
+ initDingtalkPluginConfigSchema();
40
41
  api.registerChannel({ plugin: dingtalkPlugin });
41
42
  registerGatewayMethods(api);
42
43
  }
43
44
  //#endregion
44
- export { register as default, dingtalkPlugin, registerGatewayMethods, setDingtalkRuntime };
45
+ export { register as default, dingtalkPlugin, initDingtalkPluginConfigSchema, registerGatewayMethods, setDingtalkRuntime };
@@ -0,0 +1,2 @@
1
+ import { a as processVideoMarkers, i as processRawMediaPaths, l as toLocalPath, s as sendFileProactive } from "./media-DUMfXnwJ.mjs";
2
+ export { processRawMediaPaths, processVideoMarkers, sendFileProactive, toLocalPath };
@@ -32,7 +32,6 @@ function toLocalPath(raw) {
32
32
  if (filePath.startsWith("file://")) filePath = filePath.replace("file://", "");
33
33
  else if (filePath.startsWith("MEDIA:")) filePath = filePath.replace("MEDIA:", "");
34
34
  else if (filePath.startsWith("attachment://")) filePath = filePath.replace("attachment://", "");
35
- filePath = filePath.trim();
36
35
  try {
37
36
  filePath = decodeURIComponent(filePath);
38
37
  } catch {}
@@ -436,44 +435,9 @@ async function sendFileProactive(config, target, fileInfo, mediaId, log) {
436
435
  throw err;
437
436
  }
438
437
  }
439
- /**
440
- * 发送图片消息(主动 API 模式)
441
- */
442
- async function sendImageProactive(config, target, photoURL, log) {
443
- try {
444
- const token = await (await import("./utils-DY1gFCdU.mjs")).getAccessToken(config);
445
- const { DINGTALK_API } = await import("./utils-DY1gFCdU.mjs");
446
- const msgParam = { photoURL };
447
- const body = {
448
- robotCode: String(config.clientId),
449
- msgKey: "sampleImageMsg",
450
- msgParam: JSON.stringify(msgParam)
451
- };
452
- let endpoint;
453
- if (target.type === "group") {
454
- body.openConversationId = target.openConversationId;
455
- endpoint = `${DINGTALK_API}/v1.0/robot/groupMessages/send`;
456
- } else {
457
- body.userIds = [target.userId];
458
- endpoint = `${DINGTALK_API}/v1.0/robot/oToMessages/batchSend`;
459
- }
460
- log?.info?.(`Image[Proactive] 发送图片消息: ${photoURL}`);
461
- const resp = await dingtalkHttp.post(endpoint, body, {
462
- headers: {
463
- "x-acs-dingtalk-access-token": token,
464
- "Content-Type": "application/json"
465
- },
466
- timeout: 1e4
467
- });
468
- if (resp.data?.processQueryKey) log?.info?.(`Image[Proactive] 图片消息发送成功`);
469
- else log?.warn?.(`Image[Proactive] 图片消息发送响应异常: ${JSON.stringify(resp.data)}`);
470
- } catch (err) {
471
- log?.error?.(`Image[Proactive] 发送图片消息失败: ${err.message}`);
472
- }
473
- }
474
438
  async function processRawMediaPaths(content, config, oapiToken, log, target) {
475
439
  const logPrefix = "RawMedia";
476
- const matches = Array.from(content.matchAll(/(?:^|\s)((?:MEDIA:\s*|file:\/\/|attachment:\/\/)?(?:[A-Za-z]:)?[\/\\](?:[^\/\\:\*\?"<>\|\s]+[\/\\])*[^\/\\:\*\?"<>\|\s]+\.(?:png|jpg|jpeg|gif|bmp|webp|mp4|avi|mov|wmv|flv|mkv|webm|mp3|wav|flac|aac|ogg|m4a|wma|pdf|doc|docx|xls|xlsx|ppt|pptx|txt|zip|rar|7z|tar|gz))(?:\s|$)/gi));
440
+ const matches = Array.from(content.matchAll(/(?:^|\s)((?:[A-Za-z]:)?[\/\\](?:[^\/\\:\*\?"<>\|\s]+[\/\\])*[^\/\\:\*\?"<>\|\s]+\.(?:mp4|avi|mov|wmv|flv|mkv|webm|mp3|wav|flac|aac|ogg|m4a|wma|pdf|doc|docx|xls|xlsx|ppt|pptx|txt|zip|rar|7z|tar|gz))(?:\s|$)/gi));
477
441
  if (matches.length === 0) return content;
478
442
  log?.info?.(`${logPrefix} 检测到 ${matches.length} 个裸露的本地文件路径`);
479
443
  let processedContent = content;
@@ -483,18 +447,9 @@ async function processRawMediaPaths(content, config, oapiToken, log, target) {
483
447
  const filePath = match[1].trim();
484
448
  try {
485
449
  log?.info?.(`${logPrefix} 开始处理文件: ${filePath}`);
486
- const localFilePath = toLocalPath(filePath);
487
- const ext = localFilePath.toLowerCase().split(".").pop() || "";
450
+ const ext = filePath.toLowerCase().split(".").pop() || "";
488
451
  let mediaType;
489
452
  if ([
490
- "png",
491
- "jpg",
492
- "jpeg",
493
- "gif",
494
- "bmp",
495
- "webp"
496
- ].includes(ext)) mediaType = "image";
497
- else if ([
498
453
  "mp4",
499
454
  "avi",
500
455
  "mov",
@@ -513,27 +468,24 @@ async function processRawMediaPaths(content, config, oapiToken, log, target) {
513
468
  "wma"
514
469
  ].includes(ext)) mediaType = "voice";
515
470
  else mediaType = "file";
516
- const uploadResult = await uploadMediaToDingTalk(localFilePath, mediaType, oapiToken, 20 * 1024 * 1024, log);
471
+ const uploadResult = await uploadMediaToDingTalk(filePath, mediaType, oapiToken, 20 * 1024 * 1024, log);
517
472
  if (!uploadResult) {
518
- log?.error?.(`${logPrefix} 文件上传失败: ${localFilePath}`);
519
- statusMessages.push(`⚠️ 文件上传失败: ${localFilePath}`);
473
+ log?.error?.(`${logPrefix} 文件上传失败: ${filePath}`);
474
+ statusMessages.push(`⚠️ 文件上传失败: ${filePath}`);
520
475
  continue;
521
476
  }
522
- const fileName = localFilePath.split(/[\/\\]/).pop() || "unknown";
523
- if (mediaType === "image") {
524
- if (target) await sendImageProactive(config, target, uploadResult.downloadUrl, log);
525
- statusMessages.push(`✅ 图片已发送: ${fileName}`);
526
- } else if (mediaType === "video") {
527
- const metadata = await extractVideoMetadata(localFilePath, log);
477
+ const fileName = filePath.split(/[\/\\]/).pop() || "unknown";
478
+ if (mediaType === "video") {
479
+ const metadata = await extractVideoMetadata(filePath, log);
528
480
  if (target) await sendVideoProactive(config, target, uploadResult.mediaId, fileName, log, metadata);
529
481
  statusMessages.push(`✅ 视频已发送: ${fileName}`);
530
482
  } else if (mediaType === "voice") {
531
- const durationMs = await extractAudioDuration(localFilePath, log);
483
+ const durationMs = await extractAudioDuration(filePath, log);
532
484
  if (target) await sendAudioProactive(config, target, fileName, uploadResult.downloadUrl, log, durationMs ?? void 0);
533
485
  statusMessages.push(`✅ 音频已发送: ${fileName}`);
534
486
  } else {
535
487
  const fileInfo = {
536
- path: localFilePath,
488
+ path: filePath,
537
489
  fileName,
538
490
  fileType: ext
539
491
  };
@@ -554,4 +506,4 @@ async function processRawMediaPaths(content, config, oapiToken, log, target) {
554
506
  return processedContent;
555
507
  }
556
508
  //#endregion
557
- export { processVideoMarkers as a, sendImageProactive as c, uploadMediaToDingTalk as d, __exportAll as f, processRawMediaPaths as i, sendVideoProactive as l, extractVideoMetadata as n, sendAudioProactive as o, extractVideoThumbnail as r, sendFileProactive as s, VIDEO_MARKER_PATTERN as t, toLocalPath as u };
509
+ export { processVideoMarkers as a, sendVideoProactive as c, __exportAll as d, processRawMediaPaths as i, toLocalPath as l, extractVideoMetadata as n, sendAudioProactive as o, extractVideoThumbnail as r, sendFileProactive as s, VIDEO_MARKER_PATTERN as t, uploadMediaToDingTalk as u };
@@ -1,14 +1,14 @@
1
- import { d as uploadMediaToDingTalk } from "./media-CqiIK9ZD.mjs";
1
+ import { u as uploadMediaToDingTalk } from "./media-DUMfXnwJ.mjs";
2
2
  import { a as resolveDingtalkAccount } from "./accounts-CF4oK_HZ.mjs";
3
- import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-CJCdIYOh.mjs";
3
+ import { r as CHANNEL_ID, t as getDingtalkRuntime } from "./runtime-b4xvqwW6.mjs";
4
4
  import { n as createLoggerFromConfig } from "./logger-BDWwViGT.mjs";
5
5
  import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
6
6
  import { i as getOapiAccessToken } from "./utils-CIfI_3Jh.mjs";
7
- import { a as sendTextMessage, d as isQpsLimitError, f as streamAICard, i as sendProactive, l as createAICardForTarget, r as sendMessage, t as sendMarkdownMessage, u as finishAICard } from "./messaging-BNycOrEX.mjs";
7
+ import { a as sendTextMessage, d as isQpsLimitError, f as streamAICard, i as sendProactive, l as createAICardForTarget, r as sendMessage, t as sendMarkdownMessage, u as finishAICard } from "./messaging-CyIJY4h2.mjs";
8
8
  import { a as QUEUE_BUSY_ACK_PHRASES, n as normalizeSlashCommand, t as buildSessionContext } from "./session-DJ4jYqPv.mjs";
9
9
  import { d as recallEmotionReply, o as getAccessToken, r as addEmotionReply, s as getOapiAccessToken$1, t as DINGTALK_API } from "./utils-legacy-CALCPP1t.mjs";
10
10
  import "./chunk-upload-6p9cf3UB.mjs";
11
- import { a as VIDEO_MARKER_PATTERN, i as LOCAL_IMAGE_RE, o as toLocalPath, r as FILE_MARKER_PATTERN, s as uploadMediaToDingTalk$1, t as AUDIO_MARKER_PATTERN } from "./common-PeRcixHR.mjs";
11
+ import { a as VIDEO_MARKER_PATTERN, i as LOCAL_IMAGE_RE, o as toLocalPath, r as FILE_MARKER_PATTERN, s as uploadMediaToDingTalk$1, t as AUDIO_MARKER_PATTERN } from "./common-BGJlWkEp.mjs";
12
12
  import * as fs from "fs";
13
13
  import * as path from "path";
14
14
  import * as os from "node:os";
@@ -204,7 +204,7 @@ async function uploadAndReplaceFileMarkers(content, sessionWebhook, config, oapi
204
204
  }
205
205
  //#endregion
206
206
  //#region src/reply-dispatcher.ts
207
- const { createReplyPrefixOptions = () => void 0, createTypingCallbacks = () => ({}), logTypingFailure = () => {} } = await import("openclaw/plugin-sdk/channel-runtime").catch(() => null) ?? {};
207
+ const { createReplyPrefixOptions, createTypingCallbacks, logTypingFailure } = await import("openclaw/plugin-sdk/channel-runtime");
208
208
  function createDingtalkReplyDispatcher(params) {
209
209
  const core = getDingtalkRuntime();
210
210
  const { cfg, agentId, conversationId, senderId, isDirect, accountId, sessionWebhook, asyncMode = false, preCreatedCard } = params;
@@ -212,18 +212,13 @@ function createDingtalkReplyDispatcher(params) {
212
212
  cfg,
213
213
  accountId
214
214
  });
215
- const resolvedConfig = {
216
- ...account.config,
217
- ...account.clientId != null ? { clientId: account.clientId } : {},
218
- ...account.clientSecret != null ? { clientSecret: account.clientSecret } : {}
219
- };
220
215
  const { onModelSelected, ...prefixOptions } = createReplyPrefixOptions({
221
216
  cfg,
222
217
  agentId,
223
218
  channel: CHANNEL_ID,
224
219
  accountId
225
220
  });
226
- const log = createLoggerFromConfig(resolvedConfig, `DingTalk:${accountId}`);
221
+ const log = createLoggerFromConfig(account.config, `DingTalk:${accountId}`);
227
222
  let currentCardTarget = null;
228
223
  let accumulatedText = "";
229
224
  const deliveredFinalTexts = /* @__PURE__ */ new Set();
@@ -256,7 +251,7 @@ function createDingtalkReplyDispatcher(params) {
256
251
  }[errorType];
257
252
  log.warn(`[DingTalk][Fallback] ${errorMessage}, error: ${originalError}`);
258
253
  try {
259
- await sendMessage(resolvedConfig, sessionWebhook, errorMessage, {
254
+ await sendMessage(account.config, sessionWebhook, errorMessage, {
260
255
  useMarkdown: false,
261
256
  log: params.runtime.log
262
257
  });
@@ -318,7 +313,7 @@ function createDingtalkReplyDispatcher(params) {
318
313
  openConversationId: conversationId
319
314
  };
320
315
  log.info(`[DingTalk][startStreaming] 目标:${JSON.stringify(target)}`);
321
- const card = await createAICardForTarget(resolvedConfig, target, log);
316
+ const card = await createAICardForTarget(account.config, target, log);
322
317
  currentCardTarget = card;
323
318
  accumulatedText = "";
324
319
  if (card) log.info(`[DingTalk][startStreaming] ✅ AI Card 创建成功`);
@@ -357,12 +352,12 @@ function createDingtalkReplyDispatcher(params) {
357
352
  log.info(`[DingTalk][closeStreaming] 开始处理媒体文件,target=${JSON.stringify(target)}`);
358
353
  if (oapiToken) {
359
354
  finalText = await processLocalImages(finalText, oapiToken, log);
360
- finalText = await processVideoMarkers(finalText, "", resolvedConfig, oapiToken, log, true, target);
361
- finalText = await processAudioMarkers(finalText, "", resolvedConfig, oapiToken, log, true, target);
362
- finalText = await uploadAndReplaceFileMarkers(finalText, "", resolvedConfig, oapiToken, log, true, target);
355
+ finalText = await processVideoMarkers(finalText, "", account.config, oapiToken, log, true, target);
356
+ finalText = await processAudioMarkers(finalText, "", account.config, oapiToken, log, true, target);
357
+ finalText = await uploadAndReplaceFileMarkers(finalText, "", account.config, oapiToken, log, true, target);
363
358
  log.info(`[DingTalk][closeStreaming] 准备调用 processRawMediaPaths`);
364
- const { processRawMediaPaths } = await import("./media-F5D9__w3.mjs");
365
- finalText = await processRawMediaPaths(finalText, resolvedConfig, oapiToken, log, target);
359
+ const { processRawMediaPaths } = await import("./media-DEuF7r3G.mjs");
360
+ finalText = await processRawMediaPaths(finalText, account.config, oapiToken, log, target);
366
361
  log.info(`[DingTalk][closeStreaming] processRawMediaPaths 处理完成`);
367
362
  } else log.warn(`[DingTalk][closeStreaming] oapiToken 为空,跳过媒体处理`);
368
363
  try {
@@ -393,14 +388,14 @@ function createDingtalkReplyDispatcher(params) {
393
388
  }
394
389
  log.info(`[DingTalk][closeStreaming] 准备调用 finishAICard,文本长度=${finalText.length}`);
395
390
  log.debug(`[DingTalk][closeStreaming] 最终发送内容长度=${finalText.length}`);
396
- await finishAICard(cardSnapshot, finalText, resolvedConfig, log);
391
+ await finishAICard(cardSnapshot, finalText, account.config, log);
397
392
  log.info(`[DingTalk][closeStreaming] ✅ AI Card 关闭成功`);
398
393
  } catch (error) {
399
394
  log.error(`[DingTalk][closeStreaming] ❌ AI Card 关闭失败:${error?.message || String(error)}`);
400
395
  await sendFallbackErrorMessage("mediaProcess", error?.message || String(error));
401
396
  if (accumulatedText.trim()) try {
402
397
  log.info(`[DingTalk][closeStreaming] 降级发送普通消息`);
403
- await sendMessage(resolvedConfig, sessionWebhook, accumulatedText, {
398
+ await sendMessage(account.config, sessionWebhook, accumulatedText, {
404
399
  useMarkdown: true,
405
400
  log: params.runtime.log
406
401
  });
@@ -437,8 +432,8 @@ function createDingtalkReplyDispatcher(params) {
437
432
  const oapiToken = await getOapiAccessToken(account.config);
438
433
  if (oapiToken) {
439
434
  log.info(`[DingTalk][deliver] 检测到 final 响应,准备处理裸露文件路径`);
440
- const { processRawMediaPaths } = await import("./media-F5D9__w3.mjs");
441
- text = await processRawMediaPaths(text, resolvedConfig, oapiToken, log, target);
435
+ const { processRawMediaPaths } = await import("./media-DEuF7r3G.mjs");
436
+ text = await processRawMediaPaths(text, account.config, oapiToken, log, target);
442
437
  log.info(`[DingTalk][deliver] 裸露文件路径处理完成`);
443
438
  }
444
439
  } catch (err) {
@@ -472,7 +467,7 @@ function createDingtalkReplyDispatcher(params) {
472
467
  if (now - lastUpdateTime >= updateInterval) {
473
468
  lastUpdateTime = now;
474
469
  try {
475
- await streamAICard(currentCardTarget, text, false, resolvedConfig, log);
470
+ await streamAICard(currentCardTarget, text, false, account.config, log);
476
471
  log.info(`[DingTalk][deliver] ✅ block 更新到 AI Card 成功`);
477
472
  } catch (streamErr) {
478
473
  log.error(`[DingTalk][deliver] ❌ block 更新 AI Card 失败:${streamErr.message}`);
@@ -495,15 +490,15 @@ function createDingtalkReplyDispatcher(params) {
495
490
  if (info?.kind === "final") {
496
491
  log.info(`[DingTalk][deliver] 降级到非流式发送,文本长度=${text.length}, isTextMode=${isTextMode}, groupReplyMode=${groupReplyMode}`);
497
492
  try {
498
- for (const chunk of core.channel.text.chunkTextWithMode(text, textChunkLimit, chunkMode)) if (isTextMode) if (groupReplyMode === "markdown") await sendMarkdownMessage(resolvedConfig, sessionWebhook, chunk.split("\n")[0]?.replace(/^[#*\s\->]+/, "").slice(0, 20) || "Message", chunk, {
493
+ for (const chunk of core.channel.text.chunkTextWithMode(text, textChunkLimit, chunkMode)) if (isTextMode) if (groupReplyMode === "markdown") await sendMarkdownMessage(account.config, sessionWebhook, chunk.split("\n")[0]?.replace(/^[#*\s\->]+/, "").slice(0, 20) || "Message", chunk, {
499
494
  cfg,
500
495
  detectBareAliases: true
501
496
  });
502
- else await sendTextMessage(resolvedConfig, sessionWebhook, chunk, {
497
+ else await sendTextMessage(account.config, sessionWebhook, chunk, {
503
498
  cfg,
504
499
  detectBareAliases: true
505
500
  });
506
- else await sendMessage(resolvedConfig, sessionWebhook, chunk, {
501
+ else await sendMessage(account.config, sessionWebhook, chunk, {
507
502
  useMarkdown: true,
508
503
  log: params.runtime.log,
509
504
  cfg,
@@ -558,12 +553,12 @@ function createDingtalkReplyDispatcher(params) {
558
553
  accumulatedText = payload.text;
559
554
  const now = Date.now();
560
555
  if (now - lastUpdateTime >= updateInterval) {
561
- const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-Dv3coHhJ.mjs");
556
+ const { FILE_MARKER_PATTERN, VIDEO_MARKER_PATTERN, AUDIO_MARKER_PATTERN } = await import("./common-CGPC5bYt.mjs");
562
557
  const displayContent = accumulatedText.replace(FILE_MARKER_PATTERN, "").replace(VIDEO_MARKER_PATTERN, "").replace(AUDIO_MARKER_PATTERN, "").trim();
563
558
  log.debug(`[DingTalk][onPartialReply] 更新 AI Card,显示文本长度=${displayContent.length}`);
564
559
  lastUpdateTime = now;
565
560
  try {
566
- await streamAICard(currentCardTarget, displayContent, false, resolvedConfig, log);
561
+ await streamAICard(currentCardTarget, displayContent, false, account.config, log);
567
562
  log.debug(`[DingTalk][onPartialReply] ✅ AI Card 更新成功`);
568
563
  } catch (err) {
569
564
  if (isQpsLimitError(err)) log.warn(`[DingTalk][onPartialReply] AI Card 流式更新遇到 QPS 限流,已在内部退避重试;本次跳过,等待下一次 partial 更新补齐内容`);
@@ -1701,7 +1696,7 @@ async function handleDingTalkMessageInternal(params) {
1701
1696
  finalText = await processVideoMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
1702
1697
  finalText = await processAudioMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
1703
1698
  finalText = await uploadAndReplaceFileMarkers(finalText, "", config, oapiToken, log, true, mediaTarget);
1704
- const { processRawMediaPaths } = await import("./media-F5D9__w3.mjs");
1699
+ const { processRawMediaPaths } = await import("./media-DEuF7r3G.mjs");
1705
1700
  finalText = await processRawMediaPaths(finalText, config, oapiToken, log, mediaTarget);
1706
1701
  }
1707
1702
  const textToSend = finalText.trim() || "✅ 任务执行完成(无文本输出)";
@@ -1,4 +1,4 @@
1
- import { d as uploadMediaToDingTalk } from "./media-CqiIK9ZD.mjs";
1
+ import { u as uploadMediaToDingTalk } from "./media-DUMfXnwJ.mjs";
2
2
  import { n as createLoggerFromConfig } from "./logger-BDWwViGT.mjs";
3
3
  import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
4
4
  import { i as getOapiAccessToken, r as getAccessToken, t as DINGTALK_API } from "./utils-CIfI_3Jh.mjs";
@@ -771,7 +771,7 @@ async function sendMediaToDingTalk(params) {
771
771
  });
772
772
  }
773
773
  let resolvedMediaUrl = mediaUrl;
774
- const { toLocalPath } = await import("./media-F5D9__w3.mjs");
774
+ const { toLocalPath } = await import("./media-DEuF7r3G.mjs");
775
775
  const _fs = await import("fs");
776
776
  const _path = await import("path");
777
777
  const directPath = toLocalPath(mediaUrl);
@@ -806,7 +806,7 @@ async function sendMediaToDingTalk(params) {
806
806
  }
807
807
  if (mediaType === "video") {
808
808
  const videoMarker = `[DINGTALK_VIDEO]{"path":"${mediaUrl}"}[/DINGTALK_VIDEO]`;
809
- const { processVideoMarkers } = await import("./media-F5D9__w3.mjs");
809
+ const { processVideoMarkers } = await import("./media-DEuF7r3G.mjs");
810
810
  await processVideoMarkers(videoMarker, "", config, oapiToken, console, true, targetParam);
811
811
  if (text?.trim()) {
812
812
  const result = await sendProactive(config, targetParam, text, {
@@ -830,7 +830,7 @@ async function sendMediaToDingTalk(params) {
830
830
  fileName,
831
831
  fileType: ext || "file"
832
832
  };
833
- const { sendFileProactive } = await import("./media-F5D9__w3.mjs");
833
+ const { sendFileProactive } = await import("./media-DEuF7r3G.mjs");
834
834
  await sendFileProactive(config, targetParam, fileInfo, uploadResult.mediaId, log);
835
835
  return {
836
836
  ok: true,