@dingtalk-real-ai/dingtalk-connector 0.8.3-beta.1 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,9 +5,12 @@ 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.3-beta.1] - 2026-03-24
8
+ ## [0.8.4] - 2026-03-24
9
9
 
10
10
  ### 修复 / Fixes
11
+ - 🐛 **群聊消息处理崩溃** - 修复群聊时报错 `TypeError: Cannot read properties of undefined (reading 'config')` 导致 Agent 无法回复的问题。根因是 `src/policy.ts` 中 `resolveDingtalkGroupToolPolicy` 函数的参数签名与 OpenClaw SDK 的 `ChannelGroupContext` 接口不匹配,函数期望接收 `account: ResolvedDingtalkAccount`,但框架实际传入 `{ cfg, groupId, accountId, ... }`,导致 `account` 为 `undefined`。现已修正参数签名,内部通过 `resolveDingtalkAccount()` 正确获取账号信息。单聊不受影响。
12
+ **Group chat message processing crash** - Fixed `TypeError: Cannot read properties of undefined (reading 'config')` crash in group chats. Root cause: `resolveDingtalkGroupToolPolicy` in `src/policy.ts` had a parameter signature mismatch with the OpenClaw SDK's `ChannelGroupContext` interface. Fixed by correcting the parameter signature and resolving the account internally via `resolveDingtalkAccount()`. Direct messages were unaffected.
13
+
11
14
  - 🐛 **兼容旧版 OpenClaw Gateway(createPluginRuntimeStore 缺失)** - 修复在旧版 OpenClaw Gateway 上加载插件时报错 `TypeError: (0 , _pluginSdk.createPluginRuntimeStore) is not a function` 的问题。根因是 `src/runtime.ts` 直接从 `openclaw/plugin-sdk` 导入 `createPluginRuntimeStore`,而该函数在旧版 SDK 中并不存在。现已替换为内联实现的 `createRuntimeStore`,功能完全等价,兼容所有版本的 OpenClaw
12
15
  **Compatible with older OpenClaw Gateway (missing createPluginRuntimeStore)** - Fixed `TypeError: (0 , _pluginSdk.createPluginRuntimeStore) is not a function` when loading the plugin on older OpenClaw Gateway versions. Root cause: `src/runtime.ts` imported `createPluginRuntimeStore` from `openclaw/plugin-sdk`, which doesn't exist in older SDK versions. Replaced with an inline `createRuntimeStore` implementation that is fully equivalent and compatible with all OpenClaw versions
13
16
 
package/README.md CHANGED
@@ -39,6 +39,38 @@
39
39
  ```
40
40
  预期输出:`✓ Gateway is running on http://127.0.0.1:18789`
41
41
 
42
+ ### ⚠️ 版本兼容性要求
43
+
44
+ **重要**:dingtalk-connector v0.8.4+ 需要 **OpenClaw SDK v2026.3.22 或更高版本**。
45
+
46
+ | dingtalk-connector 版本 | 最低 OpenClaw SDK 版本 | 说明 |
47
+ |------------------------|----------------------|------|
48
+ | v0.8.4+ | v2026.3.22+ | 使用新版 SDK API,支持更完善的路由和会话管理 |
49
+ | v0.8.3 及以下 | v2026.3.x | 兼容旧版 SDK |
50
+
51
+ **如何检查版本**:
52
+ ```bash
53
+ # 检查 OpenClaw 版本
54
+ openclaw --version
55
+
56
+ # 检查插件版本
57
+ openclaw plugins list
58
+ ```
59
+
60
+ **如何升级**:
61
+ ```bash
62
+ # 升级 OpenClaw 到最新版本
63
+ npm install -g openclaw@latest
64
+
65
+ # 或使用 yarn
66
+ yarn global add openclaw@latest
67
+ ```
68
+
69
+ **版本不兼容时的表现**:
70
+ - 插件加载时会显示详细的错误提示
71
+ - 提示信息会包含升级命令和降级方案
72
+ - 插件会自动停止加载,不影响其他插件
73
+
42
74
  ### 2. 钉钉企业账号
43
75
 
44
76
  - 你需要一个钉钉企业账号来创建企业内部应用
package/index.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * 钉钉企业内部机器人插件,使用 Stream 模式连接,支持 AI Card 流式响应。
5
5
  * 已迁移到 OpenClaw SDK,支持多账号、安全策略等完整功能。
6
6
  *
7
- * Last updated: 2026-03-18 17:00:00
7
+ * Last updated: 2026-03-24
8
8
  */
9
9
 
10
10
  /**
@@ -19,10 +19,65 @@ import { dingtalkPlugin } from "./src/channel.ts";
19
19
  import { setDingtalkRuntime } from "./src/runtime.ts";
20
20
  import { registerGatewayMethods } from "./src/gateway-methods.ts";
21
21
 
22
+ /**
23
+ * 检查 OpenClaw SDK 版本兼容性
24
+ * @param api OpenClaw Plugin API
25
+ * @returns true 表示版本兼容,false 表示版本过低
26
+ */
27
+ function checkSdkVersion(api: OpenClawPluginApi): boolean {
28
+ try {
29
+ // 检查是否存在新版 SDK 的关键 API
30
+ // v0.8.4+ 需要 core.channel.routing.buildAgentSessionKey 方法
31
+ const hasNewRoutingApi = !!(api.runtime?.core?.channel?.routing?.buildAgentSessionKey);
32
+
33
+ if (!hasNewRoutingApi) {
34
+ console.error('\n' + '='.repeat(80));
35
+ console.error('❌ OpenClaw SDK 版本过低');
36
+ console.error('='.repeat(80));
37
+ console.error('');
38
+ console.error('dingtalk-connector v0.8.4+ 需要 OpenClaw SDK v2026.3.22 或更高版本。');
39
+ console.error('');
40
+ console.error('当前 OpenClaw SDK 版本过低,缺少必要的 API:');
41
+ console.error(' - core.channel.routing.buildAgentSessionKey');
42
+ console.error('');
43
+ console.error('请升级 OpenClaw 到最新版本:');
44
+ console.error('');
45
+ console.error(' npm install -g openclaw@latest');
46
+ console.error(' # 或');
47
+ console.error(' yarn global add openclaw@latest');
48
+ console.error('');
49
+ console.error('升级后重启 OpenClaw Gateway 即可。');
50
+ console.error('');
51
+ console.error('如果需要使用旧版 OpenClaw,请降级 dingtalk-connector:');
52
+ console.error('');
53
+ console.error(' npm install @dingtalk-real-ai/dingtalk-connector@0.8.3');
54
+ console.error('');
55
+ console.error('='.repeat(80));
56
+ console.error('');
57
+ return false;
58
+ }
59
+
60
+ return true;
61
+ } catch (err) {
62
+ console.error('检查 OpenClaw SDK 版本时出错:', err);
63
+ return false;
64
+ }
65
+ }
66
+
22
67
  export default function register(api: OpenClawPluginApi) {
68
+ // 版本兼容性检查
69
+ if (!checkSdkVersion(api)) {
70
+ console.error('[dingtalk-connector] 插件加载失败:OpenClaw SDK 版本不兼容');
71
+ console.error('[dingtalk-connector] 请按照上述提示升级 OpenClaw 或降级 dingtalk-connector');
72
+ // 不抛出异常,避免影响其他插件加载,但不注册任何功能
73
+ return;
74
+ }
75
+
23
76
  setDingtalkRuntime(api.runtime);
24
77
  api.registerChannel({ plugin: dingtalkPlugin });
25
78
 
26
79
  // 注册 Gateway Methods
27
80
  registerGatewayMethods(api);
81
+
82
+ console.log('[dingtalk-connector] v0.8.4 已成功加载(需要 OpenClaw SDK v2026.3.22+)');
28
83
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "dingtalk-connector",
3
3
  "name": "DingTalk Channel",
4
- "version": "0.8.3-beta.1",
4
+ "version": "0.8.4",
5
5
  "description": "DingTalk (钉钉) messaging channel via Stream mode with AI Card streaming",
6
6
  "author": "DingTalk Real Team",
7
7
  "main": "index.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dingtalk-real-ai/dingtalk-connector",
3
- "version": "0.8.3-beta.1",
3
+ "version": "0.8.4",
4
4
  "description": "DingTalk (钉钉) channel connector — Stream mode with AI Card streaming",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -51,11 +51,14 @@
51
51
  "fluent-ffmpeg": "^2.1.3",
52
52
  "form-data": "^4.0.0",
53
53
  "mammoth": "^1.8.0",
54
- "openclaw": "^2026.3.23-2",
54
+ "openclaw": "^2026.3.22",
55
55
  "pako": "^2.1.0",
56
56
  "pdf-parse": "^1.1.1",
57
57
  "zod": "^3.22.0"
58
58
  },
59
+ "peerDependencies": {
60
+ "openclaw": ">=2026.3.22"
61
+ },
59
62
  "devDependencies": {
60
63
  "@types/node": "^20.19.37",
61
64
  "@vitest/coverage-v8": "^2.0.0",
@@ -15,7 +15,25 @@
15
15
  * - 钉钉 API 调用(accessToken、文件下载)
16
16
  * - 与 OpenClaw 框架集成(bindings、runtime)
17
17
  */
18
- import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "openclaw/plugin-sdk";
18
+ // 类型定义
19
+ interface ClawdbotConfig {
20
+ [key: string]: any;
21
+ }
22
+
23
+ interface RuntimeEnv {
24
+ log?: (...args: any[]) => void;
25
+ error?: (...args: any[]) => void;
26
+ warn?: (...args: any[]) => void;
27
+ debug?: (...args: any[]) => void;
28
+ info?: (...args: any[]) => void;
29
+ [key: string]: any;
30
+ }
31
+
32
+ interface HistoryEntry {
33
+ role: string;
34
+ content: string;
35
+ [key: string]: any;
36
+ }
19
37
  import type { ResolvedDingtalkAccount, DingtalkConfig } from "../types/index.ts";
20
38
  import {
21
39
  buildSessionContext,
@@ -1101,7 +1119,7 @@ export async function handleDingTalkMessageInternal(params: HandleMessageParams)
1101
1119
  );
1102
1120
 
1103
1121
  // ✅ 处理裸露的本地文件路径(绕过 OpenClaw SDK 的 bug)
1104
- const { processRawMediaPaths } = await import('../services/media.js');
1122
+ const { processRawMediaPaths } = await import('../services/media');
1105
1123
  finalText = await processRawMediaPaths(
1106
1124
  finalText,
1107
1125
  config,
package/src/policy.ts CHANGED
@@ -1,17 +1,30 @@
1
- import type { ToolPolicy } from "openclaw/plugin-sdk";
2
- import type { ResolvedDingtalkAccount } from "./types/index.ts";
1
+ // 类型定义
2
+ interface ClawdbotConfig {
3
+ [key: string]: any;
4
+ }
5
+
6
+ interface ToolPolicy {
7
+ allow?: string[];
8
+ deny?: string[];
9
+ }
10
+ import { resolveDingtalkAccount } from "./config/accounts.ts";
3
11
 
4
12
  export function resolveDingtalkGroupToolPolicy(params: {
5
- account: ResolvedDingtalkAccount;
6
- groupId: string;
13
+ cfg: ClawdbotConfig;
14
+ groupId?: string | null;
15
+ accountId?: string | null;
7
16
  }): ToolPolicy | undefined {
8
- const { account, groupId } = params;
17
+ const { cfg, groupId, accountId } = params;
18
+
19
+ const account = resolveDingtalkAccount({ cfg, accountId });
9
20
  const dingtalkCfg = account.config;
10
21
 
11
22
  // Check group-specific policy first
12
- const groupConfig = dingtalkCfg?.groups?.[groupId];
13
- if (groupConfig?.tools) {
14
- return groupConfig.tools;
23
+ if (groupId) {
24
+ const groupConfig = dingtalkCfg?.groups?.[groupId];
25
+ if (groupConfig?.tools) {
26
+ return groupConfig.tools;
27
+ }
15
28
  }
16
29
 
17
30
  // Fall back to account-level default (allow all)
@@ -1,39 +1,49 @@
1
- // openclaw 2026.3.23+ 将 plugin-sdk 拆分为子模块,旧版本仍从主入口导出。
2
- // 使用动态 import 兼容新旧两种版本:优先尝试新版子路径,失败则回退到旧版主入口。
3
- import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk/runtime";
4
- import type { ReplyPayload } from "openclaw/plugin-sdk/reply-payload";
5
-
6
- type ChannelRuntimeModule = {
7
- createReplyPrefixOptions: typeof import("openclaw/plugin-sdk/channel-runtime").createReplyPrefixOptions;
8
- createTypingCallbacks: typeof import("openclaw/plugin-sdk/channel-runtime").createTypingCallbacks;
9
- logTypingFailure: typeof import("openclaw/plugin-sdk/channel-runtime").logTypingFailure;
10
- };
1
+ // 类型定义
2
+ interface ClawdbotConfig {
3
+ [key: string]: any;
4
+ }
5
+
6
+ interface RuntimeEnv {
7
+ log?: (...args: any[]) => void;
8
+ error?: (...args: any[]) => void;
9
+ warn?: (...args: any[]) => void;
10
+ debug?: (...args: any[]) => void;
11
+ info?: (...args: any[]) => void;
12
+ [key: string]: any;
13
+ }
14
+
15
+ interface ReplyPayload {
16
+ text?: string;
17
+ [key: string]: any;
18
+ }
19
+
20
+ // ✅ 动态导入 channel-runtime 模块
21
+ const channelRuntimeModule = await import("openclaw/plugin-sdk/channel-runtime") as any;
11
22
 
12
23
  const {
13
24
  createReplyPrefixOptions,
14
25
  createTypingCallbacks,
15
26
  logTypingFailure,
16
- }: ChannelRuntimeModule = await import("openclaw/plugin-sdk/channel-runtime").catch(
17
- () => import("openclaw/plugin-sdk") as Promise<ChannelRuntimeModule>
18
- );
27
+ } = channelRuntimeModule;
28
+
19
29
  import { resolveDingtalkAccount } from "./config/accounts.ts";
20
30
  import { getDingtalkRuntime } from "./runtime.ts";
21
31
  import type { DingtalkConfig } from "./types/index.ts";
22
32
  import {
23
33
  createAICardForTarget,
24
- streamAICard,
25
34
  finishAICard,
26
- sendMessage,
27
- type AICardTarget,
35
+ streamAICard,
28
36
  type AICardInstance,
29
- } from "./services/messaging/index.ts";
37
+ type AICardTarget,
38
+ } from "./services/messaging/card.ts";
39
+ import { sendMessage } from "./services/messaging.ts";
40
+ import { getOapiAccessToken } from "./utils/token.ts";
30
41
  import {
31
42
  processLocalImages,
32
43
  processVideoMarkers,
33
44
  processAudioMarkers,
34
45
  processFileMarkers,
35
46
  } from "./services/media/index.ts";
36
- import { getAccessToken, getOapiAccessToken } from "./utils/index.ts";
37
47
 
38
48
  // ============ 新会话命令归一化 ============
39
49
 
@@ -192,16 +202,16 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
192
202
  stop: async () => {
193
203
  // 钉钉暂不支持打字指示器
194
204
  },
195
- onStartError: (err) =>
205
+ onStartError: (err: any) =>
196
206
  logTypingFailure({
197
- log: (message) => params.runtime.log?.(message),
207
+ log: (message: any) => params.runtime.log?.(message),
198
208
  channel: "dingtalk-connector",
199
209
  action: "start",
200
210
  error: err,
201
211
  }),
202
- onStopError: (err) =>
212
+ onStopError: (err: any) =>
203
213
  logTypingFailure({
204
- log: (message) => params.runtime.log?.(message),
214
+ log: (message: any) => params.runtime.log?.(message),
205
215
  channel: "dingtalk-connector",
206
216
  action: "stop",
207
217
  error: err,
@@ -217,7 +227,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
217
227
  const chunkMode = core.channel.text.resolveChunkMode(cfg, "dingtalk-connector");
218
228
 
219
229
  // 流式 AI Card 支持
220
- const streamingEnabled = account.config?.streaming !== false;
230
+ const streamingEnabled = (account.config as any)?.streaming !== false;
221
231
  let isCreatingCard = false; // ✅ 添加创建中标志,防止并发创建
222
232
 
223
233
  const startStreaming = async () => {
@@ -243,7 +253,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
243
253
  // 这样用户看到的是同一条消息从 ACK 文案更新为最终结果,而不是多出一条消息
244
254
  if (preCreatedCard) {
245
255
  log.info(`[DingTalk][startStreaming] 复用预创建 AI Card,cardInstanceId=${preCreatedCard.cardInstanceId}`);
246
- currentCardTarget = preCreatedCard;
256
+ currentCardTarget = preCreatedCard as any;
247
257
  accumulatedText = "";
248
258
  return;
249
259
  }
@@ -261,14 +271,9 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
261
271
  const card = await createAICardForTarget(
262
272
  account.config as DingtalkConfig,
263
273
  target,
264
- {
265
- info: params.runtime.info,
266
- error: params.runtime.error,
267
- warn: params.runtime.warn,
268
- debug: params.runtime.debug,
269
- }
274
+ params.runtime as any
270
275
  );
271
- currentCardTarget = card;
276
+ currentCardTarget = card as any;
272
277
  accumulatedText = "";
273
278
 
274
279
  if (card) {
@@ -284,7 +289,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
284
289
  }
285
290
  };
286
291
 
287
- const closeStreaming = async () => {
292
+ const closeStreaming: () => Promise<void> = async () => {
288
293
  if (!currentCardTarget) {
289
294
  log.info(`[DingTalk][closeStreaming] 无 AI Card,跳过关闭`);
290
295
  return;
@@ -347,7 +352,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
347
352
 
348
353
  // ✅ 处理裸露的本地文件路径(绕过 OpenClaw SDK 的 bug)
349
354
  log.info(`[DingTalk][closeStreaming] 准备调用 processRawMediaPaths`);
350
- const { processRawMediaPaths } = await import('./services/media.js');
355
+ const { processRawMediaPaths } = await import('./services/media');
351
356
  finalText = await processRawMediaPaths(
352
357
  finalText,
353
358
  account.config as DingtalkConfig,
@@ -362,14 +367,9 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
362
367
 
363
368
  log.info(`[DingTalk][closeStreaming] 准备调用 finishAICard,文本长度=${finalText.length}`);
364
369
  await finishAICard(
365
- currentCardTarget as AICardInstance,
370
+ currentCardTarget as any,
366
371
  finalText,
367
- {
368
- info: params.runtime.info,
369
- error: params.runtime.error,
370
- warn: params.runtime.warn,
371
- debug: params.runtime.debug,
372
- }
372
+ params.runtime as any
373
373
  );
374
374
  log.info(`[DingTalk][closeStreaming] ✅ AI Card 关闭成功`);
375
375
  } catch (error: any) {
@@ -429,7 +429,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
429
429
  const oapiToken = await getOapiAccessToken(account.config as DingtalkConfig);
430
430
  if (oapiToken) {
431
431
  log.info(`[DingTalk][deliver] 检测到 final 响应,准备处理裸露文件路径`);
432
- const { processRawMediaPaths } = await import('./services/media.js');
432
+ const { processRawMediaPaths } = await import('./services/media');
433
433
  text = await processRawMediaPaths(
434
434
  text,
435
435
  account.config as DingtalkConfig,
@@ -479,10 +479,10 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
479
479
  log.info(`[DingTalk][deliver] 流式更新 AI Card,累积文本长度=${accumulatedText.length}`);
480
480
  try {
481
481
  await streamAICard(
482
- currentCardTarget as AICardInstance,
482
+ currentCardTarget as any,
483
483
  accumulatedText,
484
484
  false,
485
- params.runtime.log
485
+ params.runtime as any
486
486
  );
487
487
  } catch (streamErr: any) {
488
488
  log.error(`[DingTalk][deliver] ❌ streamAICard 失败:${streamErr.message}`);
@@ -655,15 +655,10 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
655
655
 
656
656
  try {
657
657
  await streamAICard(
658
- currentCardTarget as AICardInstance,
658
+ currentCardTarget as any,
659
659
  displayContent,
660
660
  false,
661
- {
662
- info: params.runtime.info,
663
- error: params.runtime.error,
664
- warn: params.runtime.warn,
665
- debug: params.runtime.debug,
666
- }
661
+ params.runtime as any
667
662
  );
668
663
  lastUpdateTime = now;
669
664
  log.debug(`[DingTalk][onPartialReply] ✅ AI Card 更新成功`);
@@ -4,7 +4,7 @@
4
4
  * 完全独立的辅助函数,不依赖任何外部 SDK。
5
5
  */
6
6
 
7
- import type { SecretInput, SecretInputRef } from "./types/index.ts";
7
+ import type { SecretInput, SecretInputRef } from "./types.ts";
8
8
 
9
9
  // ============================================================================
10
10
  // 账号 ID 处理
@@ -43,7 +43,7 @@ export async function processAudioMarkers(
43
43
  continue;
44
44
  }
45
45
  const uploadResult = await uploadMediaToDingTalk(absPath, 'voice', oapiToken, 20 * 1024 * 1024, log);
46
- result = result.replace(full, uploadResult ? `[音频已上传:${uploadResult.downloadUrl}]` : '⚠️ 音频上传失败');
46
+ result = result.replace(full, uploadResult ? `[音频已上传:${uploadResult}]` : '⚠️ 音频上传失败');
47
47
  } catch {
48
48
  log?.warn?.(`${logPrefix} 解析音频标记失败:${match[1]}`);
49
49
  result = result.replace(full, '');
@@ -67,21 +67,15 @@ export async function enableUploadTransaction(
67
67
  form.append('file_name', fileName);
68
68
  form.append('file_size', fileSize.toString());
69
69
 
70
- const uploadResp = await dingtalkUploadHttp.post(
71
- `${DINGTALK_OAPI}/cspace/add_chunk`,
72
- chunkData,
73
- {
74
- params: {
75
- access_token: oapiToken,
76
- agent_id: config.agentId || config.clientId,
77
- transaction_id: transactionId,
78
- chunk_sequence: i,
79
- },
80
- headers: { 'Content-Type': 'application/octet-stream' },
81
- timeout: 60_000,
82
- maxBodyLength: Infinity,
83
- },
84
- );
70
+ const resp = await dingtalkOapiHttp.post<UploadTransactionResponse>(
71
+ `${DINGTALK_OAPI}/file/upload/transaction/enable`,
72
+ form,
73
+ {
74
+ params: { access_token: oapiToken },
75
+ headers: form.getHeaders(),
76
+ timeout: 60_000,
77
+ }
78
+ );
85
79
 
86
80
  if (resp.data.errcode === 0) {
87
81
  log.info(`事务开启成功,upload_id: ${resp.data.upload_id}`);
@@ -129,20 +123,15 @@ export async function uploadFileBlock(
129
123
  contentType: 'application/octet-stream',
130
124
  });
131
125
 
132
- const commitResp = await dingtalkOapiHttp.post(
133
- `${DINGTALK_OAPI}/cspace/commit`,
134
- null,
135
- {
136
- params: {
137
- access_token: oapiToken,
138
- agent_id: config.agentId || config.clientId,
139
- transaction_id: transactionId,
140
- file_size: fileSize,
141
- chunk_numbers: totalChunks,
142
- },
143
- timeout: 30_000,
144
- },
145
- );
126
+ const resp = await dingtalkOapiHttp.post<UploadBlockResponse>(
127
+ `${DINGTALK_OAPI}/file/upload/chunk`,
128
+ form,
129
+ {
130
+ params: { access_token: oapiToken },
131
+ headers: form.getHeaders(),
132
+ timeout: 60_000,
133
+ }
134
+ );
146
135
 
147
136
  if (resp.data.errcode === 0) {
148
137
  log.info(`块 ${chunkNumber} 上传成功`);
@@ -93,7 +93,7 @@ export async function uploadMediaToDingTalk(
93
93
  if ((mediaType === 'video' || mediaType === 'file') && fileSize > CHUNK_CONFIG.SIZE_THRESHOLD) {
94
94
  log?.info?.(`文件超过 20MB,使用分块上传:${absPath} (${fileSizeMB}MB)`);
95
95
  try {
96
- const { uploadLargeFileByChunks } = await import('./chunk-upload.js');
96
+ const { uploadLargeFileByChunks } = await import('./chunk-upload');
97
97
  const downloadCode = await uploadLargeFileByChunks(absPath, mediaType, oapiToken, debugEnabled);
98
98
  if (downloadCode) {
99
99
  log?.info?.(`分块上传成功:${absPath}, download_code: ${downloadCode}`);
@@ -126,10 +126,10 @@ export async function uploadMediaToDingTalk(
126
126
  log?.info?.(`上传文件:${absPath} (${fileSizeMB}MB), uploadType=${uploadType}`);
127
127
  const resp = await dingtalkUploadHttp.post(
128
128
  `${DINGTALK_OAPI}/media/upload`,
129
- formData,
129
+ form,
130
130
  {
131
131
  params: { access_token: oapiToken, type: mediaType },
132
- headers: formData.getHeaders(),
132
+ headers: form.getHeaders(),
133
133
  timeout: 60_000,
134
134
  maxBodyLength: Infinity,
135
135
  },
@@ -59,7 +59,7 @@ export async function processFileMarkers(
59
59
  const fileData = JSON.parse(match[1]);
60
60
  const absPath = toLocalPath(fileData.path);
61
61
  const uploadResult = await uploadMediaToDingTalk(absPath, 'file', oapiToken, 20 * 1024 * 1024, log);
62
- result = result.replace(full, uploadResult ? `[文件已上传:${uploadResult.downloadUrl}]` : '⚠️ 文件上传失败');
62
+ result = result.replace(full, uploadResult ? `[文件已上传:${uploadResult}]` : '⚠️ 文件上传失败');
63
63
  } catch {
64
64
  log?.warn?.(`${logPrefix} 解析文件标记失败:${match[1]}`);
65
65
  result = result.replace(full, '');
@@ -3,7 +3,15 @@
3
3
  * 支持图片上传、本地路径处理
4
4
  */
5
5
 
6
- import type { Logger } from 'openclaw/plugin-sdk';
6
+ // 本地类型定义
7
+ interface Logger {
8
+ info?: (...args: any[]) => void;
9
+ warn?: (...args: any[]) => void;
10
+ error?: (...args: any[]) => void;
11
+ debug?: (...args: any[]) => void;
12
+ [key: string]: any;
13
+ }
14
+
7
15
  import {
8
16
  LOCAL_IMAGE_RE,
9
17
  BARE_IMAGE_PATH_RE,
@@ -35,7 +43,7 @@ export async function processLocalImages(
35
43
  const cleanPath = rawPath.replace(/\\ /g, ' ');
36
44
  const uploadResult = await uploadMediaToDingTalk(cleanPath, 'image', oapiToken, 20 * 1024 * 1024, log);
37
45
  if (uploadResult) {
38
- result = result.replace(fullMatch, `![${alt}](${uploadResult.downloadUrl})`);
46
+ result = result.replace(fullMatch, `![${alt}](${uploadResult})`);
39
47
  }
40
48
  }
41
49
  }
@@ -56,7 +64,7 @@ export async function processLocalImages(
56
64
  log?.info?.(`[DingTalk][Media] 纯文本图片:"${fullMatch}" -> path="${rawPath}"`);
57
65
  const uploadResult = await uploadMediaToDingTalk(rawPath, 'image', oapiToken, 20 * 1024 * 1024, log);
58
66
  if (uploadResult) {
59
- const replacement = `![](${uploadResult.downloadUrl})`;
67
+ const replacement = `![](${uploadResult})`;
60
68
  result = result.slice(0, match.index!) + result.slice(match.index!).replace(fullMatch, replacement);
61
69
  log?.info?.(`[DingTalk][Media] 替换纯文本路径为图片:${replacement}`);
62
70
  }
@@ -697,7 +697,7 @@ export async function sendMediaToDingTalk(params: {
697
697
  const videoMarker = `[DINGTALK_VIDEO]{"path":"${mediaUrl}"}[/DINGTALK_VIDEO]`;
698
698
 
699
699
  // 直接处理视频标记(上传并发送视频消息)
700
- const { processVideoMarkers } = await import("./media.js");
700
+ const { processVideoMarkers } = await import("./media");
701
701
  await processVideoMarkers(
702
702
  videoMarker, // 只传入标记,不包含原始文本
703
703
  "",
@@ -1,4 +1,11 @@
1
- import type { BaseProbeResult } from "openclaw/plugin-sdk";
1
+ // 本地类型定义
2
+ interface BaseProbeResult<T = any> {
3
+ ok: boolean;
4
+ error?: string;
5
+ data?: T;
6
+ [key: string]: any;
7
+ }
8
+
2
9
  import type {
3
10
  DingtalkConfigSchema,
4
11
  DingtalkGroupSchema,
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "lib": ["ES2022"],
6
+ "moduleResolution": "bundler",
7
+ "esModuleInterop": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "strict": false,
10
+ "skipLibCheck": true,
11
+ "resolveJsonModule": true,
12
+ "allowJs": true,
13
+ "noEmit": true,
14
+ "allowImportingTsExtensions": true,
15
+ "types": ["node"],
16
+ "noImplicitAny": false
17
+ },
18
+ "include": ["src/**/*", "index.ts"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }