@huo15/dingtalk-connector-pro 1.0.4 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/README.en.md +106 -384
  2. package/README.md +14 -18
  3. package/dist/index.js +17 -0
  4. package/dist/openclaw.plugin.json +498 -0
  5. package/dist/package.json +91 -0
  6. package/dist/src/channel.js +415 -0
  7. package/dist/src/config/accounts.js +182 -0
  8. package/dist/src/config/schema.js +135 -0
  9. package/dist/src/core/connection.js +561 -0
  10. package/dist/src/core/message-handler.js +1422 -0
  11. package/dist/src/core/provider.js +59 -0
  12. package/dist/src/core/state.js +49 -0
  13. package/dist/src/directory.js +53 -0
  14. package/dist/src/docs.js +209 -0
  15. package/dist/src/gateway-methods.js +360 -0
  16. package/dist/src/onboarding.js +337 -0
  17. package/dist/src/policy.js +15 -0
  18. package/dist/src/probe.js +144 -0
  19. package/dist/src/reply-dispatcher.js +435 -0
  20. package/dist/src/runtime.js +26 -0
  21. package/dist/src/sdk/helpers.js +237 -0
  22. package/dist/src/sdk/types.js +13 -0
  23. package/dist/src/secret-input.js +13 -0
  24. package/dist/src/services/media/audio.js +40 -0
  25. package/dist/src/services/media/chunk-upload.js +211 -0
  26. package/dist/src/services/media/common.js +120 -0
  27. package/dist/src/services/media/file.js +54 -0
  28. package/dist/src/services/media/image.js +59 -0
  29. package/dist/src/services/media/index.js +9 -0
  30. package/dist/src/services/media/video.js +133 -0
  31. package/dist/src/services/media.js +889 -0
  32. package/dist/src/services/messaging/card.js +234 -0
  33. package/dist/src/services/messaging/index.js +8 -0
  34. package/dist/src/services/messaging/send.js +85 -0
  35. package/dist/src/services/messaging.js +680 -0
  36. package/dist/src/targets.js +38 -0
  37. package/dist/src/types/index.js +1 -0
  38. package/dist/src/utils/agent.js +55 -0
  39. package/dist/src/utils/async.js +40 -0
  40. package/dist/src/utils/constants.js +24 -0
  41. package/dist/src/utils/http-client.js +33 -0
  42. package/dist/src/utils/index.js +7 -0
  43. package/dist/src/utils/logger.js +76 -0
  44. package/dist/src/utils/session.js +95 -0
  45. package/dist/src/utils/token.js +71 -0
  46. package/dist/src/utils/utils-legacy.js +393 -0
  47. package/index.ts +3 -3
  48. package/openclaw.plugin.json +1 -1
  49. package/package.json +16 -5
  50. package/src/channel.js +415 -0
  51. package/src/channel.ts +12 -12
  52. package/src/config/accounts.js +182 -0
  53. package/src/config/accounts.ts +2 -2
  54. package/src/config/schema.js +135 -0
  55. package/src/config/schema.ts +2 -2
  56. package/src/core/connection.js +561 -0
  57. package/src/core/connection.ts +2 -2
  58. package/src/core/message-handler.js +1422 -0
  59. package/src/core/message-handler.ts +12 -12
  60. package/src/core/provider.js +59 -0
  61. package/src/core/provider.ts +4 -4
  62. package/src/core/state.js +49 -0
  63. package/src/directory.js +53 -0
  64. package/src/directory.ts +2 -2
  65. package/src/docs.js +209 -0
  66. package/src/docs.ts +3 -3
  67. package/src/gateway-methods.js +360 -0
  68. package/src/gateway-methods.ts +5 -5
  69. package/src/onboarding.js +337 -0
  70. package/src/onboarding.ts +4 -4
  71. package/src/policy.js +15 -0
  72. package/src/policy.ts +1 -1
  73. package/src/probe.js +144 -0
  74. package/src/probe.ts +2 -2
  75. package/src/reply-dispatcher.js +435 -0
  76. package/src/reply-dispatcher.ts +9 -9
  77. package/src/runtime.js +26 -0
  78. package/src/sdk/helpers.js +237 -0
  79. package/src/sdk/helpers.ts +1 -1
  80. package/src/sdk/types.js +13 -0
  81. package/src/secret-input.js +13 -0
  82. package/src/secret-input.ts +1 -1
  83. package/src/services/media/audio.js +40 -0
  84. package/src/services/media/audio.ts +2 -2
  85. package/src/services/media/chunk-upload.js +211 -0
  86. package/src/services/media/chunk-upload.ts +2 -2
  87. package/src/services/media/common.js +120 -0
  88. package/src/services/media/common.ts +3 -3
  89. package/src/services/media/file.js +54 -0
  90. package/src/services/media/file.ts +2 -2
  91. package/src/services/media/image.js +59 -0
  92. package/src/services/media/image.ts +2 -2
  93. package/src/services/media/index.js +9 -0
  94. package/src/services/media/index.ts +6 -6
  95. package/src/services/media/video.js +133 -0
  96. package/src/services/media/video.ts +2 -2
  97. package/src/services/media.js +889 -0
  98. package/src/services/media.ts +12 -12
  99. package/src/services/messaging/card.js +234 -0
  100. package/src/services/messaging/card.ts +3 -3
  101. package/src/services/messaging/index.js +8 -0
  102. package/src/services/messaging/index.ts +3 -3
  103. package/src/services/messaging/send.js +85 -0
  104. package/src/services/messaging/send.ts +3 -3
  105. package/src/services/messaging.js +680 -0
  106. package/src/services/messaging.ts +8 -8
  107. package/src/targets.js +38 -0
  108. package/src/targets.ts +1 -1
  109. package/src/types/index.js +1 -0
  110. package/src/types/index.ts +1 -1
  111. package/src/utils/agent.js +55 -0
  112. package/src/utils/async.js +40 -0
  113. package/src/utils/constants.js +24 -0
  114. package/src/utils/http-client.js +33 -0
  115. package/src/utils/http-client.ts +1 -1
  116. package/src/utils/index.js +7 -0
  117. package/src/utils/index.ts +4 -4
  118. package/src/utils/logger.js +76 -0
  119. package/src/utils/session.js +95 -0
  120. package/src/utils/session.ts +1 -1
  121. package/src/utils/token.js +71 -0
  122. package/src/utils/token.ts +2 -2
  123. package/src/utils/utils-legacy.js +393 -0
  124. package/src/utils/utils-legacy.ts +8 -8
  125. package/CHANGELOG.md +0 -485
  126. package/SKILL.md +0 -40
  127. package/_meta.json +0 -4
  128. package/docs/AGENT_ROUTING.md +0 -335
  129. package/docs/DEAP_AGENT_GUIDE.en.md +0 -115
  130. package/docs/DEAP_AGENT_GUIDE.md +0 -115
  131. package/docs/images/dingtalk.svg +0 -1
  132. package/docs/images/image-1.png +0 -0
  133. package/docs/images/image-2.png +0 -0
  134. package/docs/images/image-3.png +0 -0
  135. package/docs/images/image-4.png +0 -0
  136. package/docs/images/image-5.png +0 -0
  137. package/docs/images/image-6.png +0 -0
  138. package/docs/images/image-7.png +0 -0
  139. package/install-beta.sh +0 -438
  140. package/install-npm.sh +0 -167
  141. package/src/hooks/init.ts +0 -16
  142. package/tsconfig.json +0 -20
@@ -3,18 +3,18 @@
3
3
  * 支持 AI Card 流式响应、普通消息、主动消息
4
4
  */
5
5
 
6
- import type { DingtalkConfig } from "../types/index.ts";
7
- import { DINGTALK_API, getAccessToken, getOapiAccessToken } from "../utils/index.ts";
8
- import { dingtalkHttp, dingtalkOapiHttp } from "../utils/http-client.ts";
9
- import { MEDIA_MSG_TYPES } from "../utils/constants.ts";
10
- import { createLoggerFromConfig } from "../utils/logger.ts";
6
+ import type { DingtalkConfig } from "../types/index.js";
7
+ import { DINGTALK_API, getAccessToken, getOapiAccessToken } from "../utils/index.js";
8
+ import { dingtalkHttp, dingtalkOapiHttp } from "../utils/http-client.js";
9
+ import { MEDIA_MSG_TYPES } from "../utils/constants.js";
10
+ import { createLoggerFromConfig } from "../utils/logger.js";
11
11
  import {
12
12
  processLocalImages,
13
13
  processVideoMarkers,
14
14
  processAudioMarkers,
15
15
  processFileMarkers,
16
16
  uploadMediaToDingTalk,
17
- } from "./media.ts";
17
+ } from "./media.js";
18
18
  // ✅ 导入 AI Card 相关函数,避免重复实现
19
19
  import {
20
20
  createAICardForTarget,
@@ -22,7 +22,7 @@ import {
22
22
  finishAICard,
23
23
  type AICardInstance,
24
24
  type AICardTarget,
25
- } from "./messaging/card.ts";
25
+ } from "./messaging/card.js";
26
26
 
27
27
  // ============ 常量 ============
28
28
  // 注意:AI Card 相关的类型和函数已移至 ./messaging/card.ts,通过上方 import 引入
@@ -762,7 +762,7 @@ export async function sendMediaToDingTalk(params: {
762
762
  };
763
763
 
764
764
  // 使用 sendFileProactive 发送文件消息
765
- const { sendFileProactive } = await import("./media.ts");
765
+ const { sendFileProactive } = await import("./media.js");
766
766
  await sendFileProactive(config, targetParam, fileInfo, uploadResult.mediaId, log);
767
767
 
768
768
  // 返回成功结果
package/src/targets.js ADDED
@@ -0,0 +1,38 @@
1
+ function stripProviderPrefix(raw) {
2
+ return raw.replace(/^(dingtalk|dd|ding):/i, "").trim();
3
+ }
4
+ export function normalizeDingtalkTarget(raw) {
5
+ const trimmed = raw.trim();
6
+ if (!trimmed) {
7
+ return null;
8
+ }
9
+ const withoutProvider = stripProviderPrefix(trimmed);
10
+ const lowered = withoutProvider.toLowerCase();
11
+ if (lowered.startsWith("user:")) {
12
+ return withoutProvider.slice("user:".length).trim() || null;
13
+ }
14
+ if (lowered.startsWith("group:")) {
15
+ return withoutProvider.slice("group:".length).trim() || null;
16
+ }
17
+ return withoutProvider;
18
+ }
19
+ export function formatDingtalkTarget(id, type) {
20
+ const trimmed = id.trim();
21
+ if (type === "group") {
22
+ return `group:${trimmed}`;
23
+ }
24
+ if (type === "user") {
25
+ return `user:${trimmed}`;
26
+ }
27
+ return trimmed;
28
+ }
29
+ export function looksLikeDingtalkId(raw) {
30
+ const trimmed = stripProviderPrefix(raw.trim());
31
+ if (!trimmed) {
32
+ return false;
33
+ }
34
+ if (/^(user|group):/i.test(trimmed)) {
35
+ return true;
36
+ }
37
+ return true;
38
+ }
package/src/targets.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { DingtalkMessageContext } from "./types/index.ts";
1
+ import type { DingtalkMessageContext } from "./types/index.js";
2
2
 
3
3
  function stripProviderPrefix(raw: string): string {
4
4
  return raw.replace(/^(dingtalk|dd|ding):/i, "").trim();
@@ -0,0 +1 @@
1
+ export {};
@@ -11,7 +11,7 @@ import type {
11
11
  DingtalkGroupSchema,
12
12
  DingtalkAccountConfigSchema,
13
13
  z,
14
- } from "../config/schema.ts";
14
+ } from "../config/schema.js";
15
15
 
16
16
  export type DingtalkConfig = z.infer<typeof DingtalkConfigSchema>;
17
17
  export type DingtalkGroupConfig = z.infer<typeof DingtalkGroupSchema>;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Agent 相关工具函数
3
+ *
4
+ * 提供 Agent 配置解析、工作空间路径解析等功能
5
+ */
6
+ import * as os from "node:os";
7
+ import * as path from "node:path";
8
+ /**
9
+ * 解析 Agent 工作空间路径
10
+ *
11
+ * 参考 OpenClaw SDK 的 resolveAgentWorkspaceDir 实现逻辑:
12
+ * 1. 优先从 agents.list 中查找用户配置的 workspace
13
+ * 2. 如果没有配置,使用默认路径规则:
14
+ * - 默认 Agent (main): ~/.openclaw/workspace
15
+ * - 其他 Agent: ~/.openclaw/workspace-{agentId}
16
+ *
17
+ * @param cfg - OpenClaw 配置对象
18
+ * @param agentId - Agent ID
19
+ * @returns Agent 工作空间的绝对路径
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * // 用户自定义工作空间
24
+ * const cfg = {
25
+ * agents: {
26
+ * list: [{ id: 'bot1', workspace: '~/my-workspace' }]
27
+ * }
28
+ * };
29
+ * resolveAgentWorkspaceDir(cfg, 'bot1'); // => '/Users/xxx/my-workspace'
30
+ *
31
+ * // 默认 Agent
32
+ * resolveAgentWorkspaceDir(cfg, 'main'); // => '/Users/xxx/.openclaw/workspace'
33
+ *
34
+ * // 其他 Agent
35
+ * resolveAgentWorkspaceDir(cfg, 'bot2'); // => '/Users/xxx/.openclaw/workspace-bot2'
36
+ * ```
37
+ */
38
+ export function resolveAgentWorkspaceDir(cfg, agentId) {
39
+ // 1. 先从 agents.list 中查找配置的 workspace
40
+ const agentConfig = cfg.agents?.list?.find((a) => a.id === agentId);
41
+ if (agentConfig?.workspace) {
42
+ // 用户配置了自定义工作空间路径
43
+ // 支持 ~ 路径展开
44
+ return agentConfig.workspace.startsWith('~')
45
+ ? path.join(os.homedir(), agentConfig.workspace.slice(1))
46
+ : agentConfig.workspace;
47
+ }
48
+ // 2. 使用默认路径规则
49
+ if (agentId === 'main' || agentId === cfg.defaultAgent) {
50
+ // 默认 Agent 使用 ~/.openclaw/workspace
51
+ return path.join(os.homedir(), '.openclaw', 'workspace');
52
+ }
53
+ // 其他 Agent 使用 ~/.openclaw/workspace-{agentId}
54
+ return path.join(os.homedir(), '.openclaw', `workspace-${agentId}`);
55
+ }
@@ -0,0 +1,40 @@
1
+ export async function raceWithTimeoutAndAbort(promise, opts) {
2
+ const { timeoutMs, abortSignal } = opts;
3
+ let timeoutId;
4
+ let abortHandler;
5
+ const timeoutOutcome = new Promise((resolve) => {
6
+ timeoutId = setTimeout(() => resolve({ kind: "timeout" }), timeoutMs);
7
+ });
8
+ const abortOutcome = abortSignal
9
+ ? new Promise((resolve) => {
10
+ if (abortSignal.aborted) {
11
+ resolve({ kind: "aborted" });
12
+ return;
13
+ }
14
+ abortHandler = () => resolve({ kind: "aborted" });
15
+ abortSignal.addEventListener("abort", abortHandler, { once: true });
16
+ })
17
+ : new Promise(() => { });
18
+ try {
19
+ const winner = await Promise.race([
20
+ promise.then((value) => ({ kind: "success", value })),
21
+ timeoutOutcome,
22
+ abortOutcome,
23
+ ]);
24
+ if (winner.kind === "success") {
25
+ return { status: "success", value: winner.value };
26
+ }
27
+ if (winner.kind === "timeout") {
28
+ return { status: "timeout" };
29
+ }
30
+ return { status: "aborted" };
31
+ }
32
+ finally {
33
+ if (timeoutId) {
34
+ clearTimeout(timeoutId);
35
+ }
36
+ if (abortSignal && abortHandler) {
37
+ abortSignal.removeEventListener("abort", abortHandler);
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 常量定义模块
3
+ */
4
+ /** 新会话触发命令 */
5
+ export const NEW_SESSION_COMMANDS = ['/new', '/reset', '/clear', '新会话', '重新开始', '清空对话'];
6
+ /**
7
+ * 媒体类消息类型集合。
8
+ *
9
+ * 这些消息类型需要通过钉钉原生消息 API 发送,不支持 AI Card 形式,
10
+ * 在 sendProactiveInternal 中会强制跳过 AI Card 路径。
11
+ */
12
+ export const MEDIA_MSG_TYPES = new Set(['image', 'voice', 'file', 'video']);
13
+ /**
14
+ * 队列繁忙时的即时 ACK 回复短语。
15
+ *
16
+ * 当消息入队时检测到上一条消息仍在处理中,立即从此列表中随机选取一条回复,
17
+ * 告知用户消息已收到并排队,避免用户以为 Bot 没有响应。
18
+ * 参考 delivery.rs 中 DINGTALK_ACK_PHRASES_BUSY_ZH_CN 的设计。
19
+ */
20
+ export const QUEUE_BUSY_ACK_PHRASES = [
21
+ '上一条还没结束,这条我已经记下,稍后按顺序继续处理。',
22
+ '当前还在忙,你的新消息已经排队,上一条完成后我马上继续。',
23
+ '我这边还在处理上一条,这条已加入队列,完成后继续处理。',
24
+ ];
@@ -0,0 +1,33 @@
1
+ /**
2
+ * HTTP 客户端配置模块
3
+ *
4
+ * 提供统一的 axios 实例,用于钉钉 API 请求。
5
+ *
6
+ * 使用方式:
7
+ * ```typescript
8
+ * import { dingtalkHttp } from './utils/http-client.js';
9
+ *
10
+ * const response = await dingtalkHttp.post('/api/endpoint', data);
11
+ * ```
12
+ */
13
+ import axios from 'axios';
14
+ /** 钉钉专用 HTTP 客户端(30 秒超时) */
15
+ export const dingtalkHttp = axios.create({
16
+ timeout: 30000,
17
+ headers: {
18
+ 'Content-Type': 'application/json',
19
+ },
20
+ });
21
+ /** 钉钉 OAPI 专用 HTTP 客户端(60 秒超时,用于媒体上传等) */
22
+ export const dingtalkOapiHttp = axios.create({
23
+ timeout: 60000,
24
+ headers: {
25
+ 'Content-Type': 'application/json',
26
+ },
27
+ });
28
+ /** 文件上传专用 HTTP 客户端(120 秒超时,无 body 大小限制) */
29
+ export const dingtalkUploadHttp = axios.create({
30
+ timeout: 120000,
31
+ maxContentLength: Infinity,
32
+ maxBodyLength: Infinity,
33
+ });
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * 使用方式:
7
7
  * ```typescript
8
- * import { dingtalkHttp } from './utils/http-client.ts';
8
+ * import { dingtalkHttp } from './utils/http-client.js';
9
9
  *
10
10
  * const response = await dingtalkHttp.post('/api/endpoint', data);
11
11
  * ```
@@ -0,0 +1,7 @@
1
+ /**
2
+ * 工具函数模块统一导出
3
+ */
4
+ export * from './constants.js';
5
+ export * from './token.js';
6
+ export * from './session.js';
7
+ export * from './logger.js';
@@ -2,7 +2,7 @@
2
2
  * 工具函数模块统一导出
3
3
  */
4
4
 
5
- export * from './constants.ts';
6
- export * from './token.ts';
7
- export * from './session.ts';
8
- export * from './logger.ts';
5
+ export * from './constants.js';
6
+ export * from './token.js';
7
+ export * from './session.js';
8
+ export * from './logger.js';
@@ -0,0 +1,76 @@
1
+ /**
2
+ * 日志工具模块
3
+ * 根据 debug 配置控制日志输出
4
+ */
5
+ /**
6
+ * 创建日志记录器
7
+ * @param debug - 是否启用 debug 模式
8
+ * @param prefix - 日志前缀
9
+ * @returns 日志记录器对象
10
+ */
11
+ export function createLogger(debug = false, prefix = '') {
12
+ const logger = {
13
+ /**
14
+ * 打印 info 级别日志
15
+ * 仅在 debug 模式下输出
16
+ */
17
+ info(...args) {
18
+ if (debug) {
19
+ if (prefix) {
20
+ console.log(`[${prefix}]`, ...args);
21
+ }
22
+ else {
23
+ console.log(...args);
24
+ }
25
+ }
26
+ },
27
+ /**
28
+ * 打印 warn 级别日志
29
+ * 始终输出
30
+ */
31
+ warn(...args) {
32
+ if (prefix) {
33
+ console.warn(`[${prefix}]`, ...args);
34
+ }
35
+ else {
36
+ console.warn(...args);
37
+ }
38
+ },
39
+ /**
40
+ * 打印 error 级别日志
41
+ * 始终输出
42
+ */
43
+ error(...args) {
44
+ if (prefix) {
45
+ console.error(`[${prefix}]`, ...args);
46
+ }
47
+ else {
48
+ console.error(...args);
49
+ }
50
+ },
51
+ /**
52
+ * 打印 debug 级别日志
53
+ * 仅在 debug 模式下输出
54
+ */
55
+ debug(...args) {
56
+ if (debug) {
57
+ if (prefix) {
58
+ console.log(`[DEBUG][${prefix}]`, ...args);
59
+ }
60
+ else {
61
+ console.log('[DEBUG]', ...args);
62
+ }
63
+ }
64
+ },
65
+ };
66
+ return logger;
67
+ }
68
+ /**
69
+ * 从配置中创建日志记录器
70
+ * @param config - 包含 debug 配置的对象(可选)
71
+ * @param prefix - 日志前缀
72
+ * @returns 日志记录器对象
73
+ */
74
+ export function createLoggerFromConfig(config, prefix = '') {
75
+ return createLogger(!!config?.debug, prefix);
76
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * 会话管理模块
3
+ * 构建 OpenClaw 标准会话上下文
4
+ */
5
+ import { NEW_SESSION_COMMANDS } from './constants.js';
6
+ /**
7
+ * 构建 OpenClaw 标准会话上下文
8
+ * 遵循 OpenClaw session.dmScope 机制,让 Gateway 根据配置自动处理会话隔离
9
+ *
10
+ * @param sharedMemoryAcrossConversations - 是否在不同会话间共享记忆(默认 false)
11
+ * - true: 所有会话共享记忆,使用 accountId 作为记忆标识
12
+ * - false: 不同会话独立记忆,使用完整的 sessionContext 作为记忆标识
13
+ */
14
+ export function buildSessionContext(params) {
15
+ const { accountId, senderId, senderName, conversationType, conversationId, groupSubject, separateSessionByConversation, groupSessionScope, sharedMemoryAcrossConversations, } = params;
16
+ const isDirect = conversationType === '1';
17
+ // peerId:真实的 peer 标识,不受任何会话隔离配置影响,专用于 bindings 路由匹配
18
+ // 群聊为 conversationId,单聊为 senderId,与配置中 match.peer.id 语义一致
19
+ const peerId = isDirect ? senderId : (conversationId || senderId);
20
+ // sharedMemoryAcrossConversations=true 时,所有会话共享记忆
21
+ // sessionPeerId 被设为 accountId 以合并记忆,peerId 仍保留真实 peer,供路由匹配使用
22
+ if (sharedMemoryAcrossConversations === true) {
23
+ return {
24
+ channel: 'dingtalk-connector',
25
+ accountId,
26
+ chatType: isDirect ? 'direct' : 'group',
27
+ peerId,
28
+ sessionPeerId: accountId, // 使用 accountId 作为 sessionPeerId,实现跨会话记忆共享
29
+ conversationId: isDirect ? undefined : conversationId,
30
+ senderName,
31
+ groupSubject: isDirect ? undefined : groupSubject,
32
+ };
33
+ }
34
+ // separateSessionByConversation=false 时,不区分单聊/群聊,按用户维度维护 session
35
+ if (separateSessionByConversation === false) {
36
+ return {
37
+ channel: 'dingtalk-connector',
38
+ accountId,
39
+ chatType: isDirect ? 'direct' : 'group',
40
+ peerId,
41
+ sessionPeerId: senderId, // 只用 senderId,不区分会话
42
+ conversationId: isDirect ? undefined : conversationId,
43
+ senderName,
44
+ groupSubject: isDirect ? undefined : groupSubject,
45
+ };
46
+ }
47
+ // 以下是 separateSessionByConversation=true(默认)的逻辑
48
+ if (isDirect) {
49
+ // 单聊:sessionPeerId 为发送者 ID,由 OpenClaw Gateway 根据 dmScope 配置处理
50
+ return {
51
+ channel: 'dingtalk-connector',
52
+ accountId,
53
+ chatType: 'direct',
54
+ peerId,
55
+ sessionPeerId: senderId,
56
+ senderName,
57
+ };
58
+ }
59
+ // 群聊:根据 groupSessionScope 配置决定会话隔离策略
60
+ if (groupSessionScope === 'group_sender') {
61
+ // 群内每个用户独立会话
62
+ return {
63
+ channel: 'dingtalk-connector',
64
+ accountId,
65
+ chatType: 'group',
66
+ peerId,
67
+ sessionPeerId: `${conversationId}:${senderId}`,
68
+ conversationId,
69
+ senderName,
70
+ groupSubject,
71
+ };
72
+ }
73
+ // 默认:整个群共享一个会话
74
+ return {
75
+ channel: 'dingtalk-connector',
76
+ accountId,
77
+ chatType: 'group',
78
+ peerId,
79
+ sessionPeerId: conversationId || senderId,
80
+ conversationId,
81
+ senderName,
82
+ groupSubject,
83
+ };
84
+ }
85
+ /**
86
+ * 检查消息是否是新会话命令
87
+ */
88
+ export function normalizeSlashCommand(text) {
89
+ const trimmed = text.trim();
90
+ const lower = trimmed.toLowerCase();
91
+ if (NEW_SESSION_COMMANDS.some((cmd) => lower === cmd.toLowerCase())) {
92
+ return '/new';
93
+ }
94
+ return text;
95
+ }
@@ -3,7 +3,7 @@
3
3
  * 构建 OpenClaw 标准会话上下文
4
4
  */
5
5
 
6
- import { NEW_SESSION_COMMANDS } from './constants.ts';
6
+ import { NEW_SESSION_COMMANDS } from './constants.js';
7
7
 
8
8
  /** OpenClaw 标准会话上下文 */
9
9
  export interface SessionContext {
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Access Token 管理模块
3
+ * 支持钉钉 API 和 OAPI 的 Token 获取和缓存
4
+ */
5
+ import { dingtalkHttp, dingtalkOapiHttp } from './http-client.js';
6
+ // ============ 常量 ============
7
+ export const DINGTALK_API = 'https://api.dingtalk.com';
8
+ export const DINGTALK_OAPI = 'https://oapi.dingtalk.com';
9
+ /**
10
+ * 按 clientId 分桶缓存,避免多账号串 token。
11
+ */
12
+ const apiTokenCache = new Map();
13
+ const oapiTokenCache = new Map();
14
+ function cacheKey(config) {
15
+ const clientId = String(config?.clientId ?? '').trim();
16
+ // 添加校验
17
+ if (!clientId) {
18
+ throw new Error('Invalid DingtalkConfig: clientId is required for token caching. ' +
19
+ 'Please ensure your configuration includes a valid clientId.');
20
+ }
21
+ return clientId;
22
+ }
23
+ /**
24
+ * 获取钉钉 Access Token(新版 API)
25
+ */
26
+ export async function getAccessToken(config) {
27
+ const now = Date.now();
28
+ const key = cacheKey(config);
29
+ const cached = apiTokenCache.get(key);
30
+ if (cached && cached.expiryMs > now + 60_000) {
31
+ return cached.token;
32
+ }
33
+ const response = await dingtalkHttp.post(`${DINGTALK_API}/v1.0/oauth2/accessToken`, {
34
+ appKey: config.clientId,
35
+ appSecret: config.clientSecret,
36
+ });
37
+ const token = response.data.accessToken;
38
+ const expireInSec = Number(response.data.expireIn ?? 0);
39
+ apiTokenCache.set(key, {
40
+ token,
41
+ expiryMs: now + expireInSec * 1000,
42
+ });
43
+ return token;
44
+ }
45
+ /**
46
+ * 获取钉钉 OAPI Access Token(旧版 API,用于媒体上传等)
47
+ */
48
+ export async function getOapiAccessToken(config) {
49
+ try {
50
+ const now = Date.now();
51
+ const key = cacheKey(config);
52
+ const cached = oapiTokenCache.get(key);
53
+ if (cached && cached.expiryMs > now + 60_000) {
54
+ return cached.token;
55
+ }
56
+ const resp = await dingtalkOapiHttp.get(`${DINGTALK_OAPI}/gettoken`, {
57
+ params: { appkey: config.clientId, appsecret: config.clientSecret },
58
+ });
59
+ if (resp.data?.errcode === 0 && resp.data?.access_token) {
60
+ const token = String(resp.data.access_token);
61
+ // 钉钉返回 expires_in(秒),拿不到就按 2 小时兜底
62
+ const expiresInSec = Number(resp.data.expires_in ?? 7200);
63
+ oapiTokenCache.set(key, { token, expiryMs: now + expiresInSec * 1000 });
64
+ return token;
65
+ }
66
+ return null;
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
@@ -3,8 +3,8 @@
3
3
  * 支持钉钉 API 和 OAPI 的 Token 获取和缓存
4
4
  */
5
5
 
6
- import type { DingtalkConfig } from '../types/index.ts';
7
- import { dingtalkHttp, dingtalkOapiHttp } from './http-client.ts';
6
+ import type { DingtalkConfig } from '../types/index.js';
7
+ import { dingtalkHttp, dingtalkOapiHttp } from './http-client.js';
8
8
 
9
9
  // ============ 常量 ============
10
10