@dingtalk-real-ai/dingtalk-connector 0.8.3 → 0.8.5

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,18 @@ 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.5] - 2026-03-24
9
+
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
+
14
+ - 🐛 **兼容旧版 OpenClaw Gateway(createPluginRuntimeStore 缺失)** - 修复在旧版 OpenClaw Gateway 上加载插件时报错 `TypeError: (0 , _pluginSdk.createPluginRuntimeStore) is not a function` 的问题。根因是 `src/runtime.ts` 直接从 `openclaw/plugin-sdk` 导入 `createPluginRuntimeStore`,而该函数在旧版 SDK 中并不存在。现已替换为内联实现的 `createRuntimeStore`,功能完全等价,兼容所有版本的 OpenClaw
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
16
+
17
+ - 🐛 **openclaw 依赖版本约束放宽** - 将 `package.json` 中的 `"openclaw": "^2026.3.0"` 改为 `"openclaw": "*"`,避免版本约束导致安装失败或与用户已安装版本冲突
18
+ **Relaxed openclaw dependency version constraint** - Changed `"openclaw": "^2026.3.0"` to `"openclaw": "*"` in `package.json` to avoid installation failures or conflicts with the user's installed version
19
+
8
20
  ## [0.8.3] - 2026-03-24
9
21
 
10
22
  ### 修复 / Fixes
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",
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",
3
+ "version": "0.8.5",
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.0",
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,31 +1,49 @@
1
- import type {
2
- ClawdbotConfig,
3
- RuntimeEnv,
4
- ReplyPayload,
5
- } from "openclaw/plugin-sdk";
6
- import {
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;
22
+
23
+ const {
7
24
  createReplyPrefixOptions,
8
25
  createTypingCallbacks,
9
26
  logTypingFailure,
10
- } from "openclaw/plugin-sdk";
27
+ } = channelRuntimeModule;
28
+
11
29
  import { resolveDingtalkAccount } from "./config/accounts.ts";
12
30
  import { getDingtalkRuntime } from "./runtime.ts";
13
31
  import type { DingtalkConfig } from "./types/index.ts";
14
32
  import {
15
33
  createAICardForTarget,
16
- streamAICard,
17
34
  finishAICard,
18
- sendMessage,
19
- type AICardTarget,
35
+ streamAICard,
20
36
  type AICardInstance,
21
- } 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";
22
41
  import {
23
42
  processLocalImages,
24
43
  processVideoMarkers,
25
44
  processAudioMarkers,
26
45
  processFileMarkers,
27
46
  } from "./services/media/index.ts";
28
- import { getAccessToken, getOapiAccessToken } from "./utils/index.ts";
29
47
 
30
48
  // ============ 新会话命令归一化 ============
31
49
 
@@ -184,16 +202,16 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
184
202
  stop: async () => {
185
203
  // 钉钉暂不支持打字指示器
186
204
  },
187
- onStartError: (err) =>
205
+ onStartError: (err: any) =>
188
206
  logTypingFailure({
189
- log: (message) => params.runtime.log?.(message),
207
+ log: (message: any) => params.runtime.log?.(message),
190
208
  channel: "dingtalk-connector",
191
209
  action: "start",
192
210
  error: err,
193
211
  }),
194
- onStopError: (err) =>
212
+ onStopError: (err: any) =>
195
213
  logTypingFailure({
196
- log: (message) => params.runtime.log?.(message),
214
+ log: (message: any) => params.runtime.log?.(message),
197
215
  channel: "dingtalk-connector",
198
216
  action: "stop",
199
217
  error: err,
@@ -209,7 +227,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
209
227
  const chunkMode = core.channel.text.resolveChunkMode(cfg, "dingtalk-connector");
210
228
 
211
229
  // 流式 AI Card 支持
212
- const streamingEnabled = account.config?.streaming !== false;
230
+ const streamingEnabled = (account.config as any)?.streaming !== false;
213
231
  let isCreatingCard = false; // ✅ 添加创建中标志,防止并发创建
214
232
 
215
233
  const startStreaming = async () => {
@@ -235,7 +253,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
235
253
  // 这样用户看到的是同一条消息从 ACK 文案更新为最终结果,而不是多出一条消息
236
254
  if (preCreatedCard) {
237
255
  log.info(`[DingTalk][startStreaming] 复用预创建 AI Card,cardInstanceId=${preCreatedCard.cardInstanceId}`);
238
- currentCardTarget = preCreatedCard;
256
+ currentCardTarget = preCreatedCard as any;
239
257
  accumulatedText = "";
240
258
  return;
241
259
  }
@@ -253,14 +271,9 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
253
271
  const card = await createAICardForTarget(
254
272
  account.config as DingtalkConfig,
255
273
  target,
256
- {
257
- info: params.runtime.info,
258
- error: params.runtime.error,
259
- warn: params.runtime.warn,
260
- debug: params.runtime.debug,
261
- }
274
+ params.runtime as any
262
275
  );
263
- currentCardTarget = card;
276
+ currentCardTarget = card as any;
264
277
  accumulatedText = "";
265
278
 
266
279
  if (card) {
@@ -276,7 +289,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
276
289
  }
277
290
  };
278
291
 
279
- const closeStreaming = async () => {
292
+ const closeStreaming: () => Promise<void> = async () => {
280
293
  if (!currentCardTarget) {
281
294
  log.info(`[DingTalk][closeStreaming] 无 AI Card,跳过关闭`);
282
295
  return;
@@ -339,7 +352,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
339
352
 
340
353
  // ✅ 处理裸露的本地文件路径(绕过 OpenClaw SDK 的 bug)
341
354
  log.info(`[DingTalk][closeStreaming] 准备调用 processRawMediaPaths`);
342
- const { processRawMediaPaths } = await import('./services/media.js');
355
+ const { processRawMediaPaths } = await import('./services/media');
343
356
  finalText = await processRawMediaPaths(
344
357
  finalText,
345
358
  account.config as DingtalkConfig,
@@ -354,14 +367,9 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
354
367
 
355
368
  log.info(`[DingTalk][closeStreaming] 准备调用 finishAICard,文本长度=${finalText.length}`);
356
369
  await finishAICard(
357
- currentCardTarget as AICardInstance,
370
+ currentCardTarget as any,
358
371
  finalText,
359
- {
360
- info: params.runtime.info,
361
- error: params.runtime.error,
362
- warn: params.runtime.warn,
363
- debug: params.runtime.debug,
364
- }
372
+ params.runtime as any
365
373
  );
366
374
  log.info(`[DingTalk][closeStreaming] ✅ AI Card 关闭成功`);
367
375
  } catch (error: any) {
@@ -421,7 +429,7 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
421
429
  const oapiToken = await getOapiAccessToken(account.config as DingtalkConfig);
422
430
  if (oapiToken) {
423
431
  log.info(`[DingTalk][deliver] 检测到 final 响应,准备处理裸露文件路径`);
424
- const { processRawMediaPaths } = await import('./services/media.js');
432
+ const { processRawMediaPaths } = await import('./services/media');
425
433
  text = await processRawMediaPaths(
426
434
  text,
427
435
  account.config as DingtalkConfig,
@@ -471,10 +479,10 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
471
479
  log.info(`[DingTalk][deliver] 流式更新 AI Card,累积文本长度=${accumulatedText.length}`);
472
480
  try {
473
481
  await streamAICard(
474
- currentCardTarget as AICardInstance,
482
+ currentCardTarget as any,
475
483
  accumulatedText,
476
484
  false,
477
- params.runtime.log
485
+ params.runtime as any
478
486
  );
479
487
  } catch (streamErr: any) {
480
488
  log.error(`[DingTalk][deliver] ❌ streamAICard 失败:${streamErr.message}`);
@@ -647,15 +655,10 @@ export function createDingtalkReplyDispatcher(params: CreateDingtalkReplyDispatc
647
655
 
648
656
  try {
649
657
  await streamAICard(
650
- currentCardTarget as AICardInstance,
658
+ currentCardTarget as any,
651
659
  displayContent,
652
660
  false,
653
- {
654
- info: params.runtime.info,
655
- error: params.runtime.error,
656
- warn: params.runtime.warn,
657
- debug: params.runtime.debug,
658
- }
661
+ params.runtime as any
659
662
  );
660
663
  lastUpdateTime = now;
661
664
  log.debug(`[DingTalk][onPartialReply] ✅ AI Card 更新成功`);
package/src/runtime.ts CHANGED
@@ -1,7 +1,32 @@
1
- import { createPluginRuntimeStore } from "openclaw/plugin-sdk";
2
1
  import type { PluginRuntime } from "openclaw/plugin-sdk";
3
2
 
3
+ /**
4
+ * 自实现的运行时存储工厂,避免依赖特定版本 openclaw 是否导出 createPluginRuntimeStore。
5
+ * 旧版 openclaw 没有导出该函数,直接 import 会导致 TypeError,因此在此处内联实现。
6
+ */
7
+ function createRuntimeStore<T>(errorMessage: string) {
8
+ let runtimeValue: T | null = null;
9
+
10
+ return {
11
+ setRuntime: (next: T): void => {
12
+ runtimeValue = next;
13
+ },
14
+ clearRuntime: (): void => {
15
+ runtimeValue = null;
16
+ },
17
+ tryGetRuntime: (): T | null => {
18
+ return runtimeValue;
19
+ },
20
+ getRuntime: (): T => {
21
+ if (runtimeValue === null) {
22
+ throw new Error(errorMessage);
23
+ }
24
+ return runtimeValue;
25
+ },
26
+ };
27
+ }
28
+
4
29
  const { setRuntime: setDingtalkRuntime, getRuntime: getDingtalkRuntime } =
5
- createPluginRuntimeStore<PluginRuntime>("DingTalk runtime not initialized");
30
+ createRuntimeStore<PluginRuntime>("DingTalk runtime not initialized");
6
31
 
7
32
  export { getDingtalkRuntime, setDingtalkRuntime };
@@ -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
+ }