@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
@@ -0,0 +1,59 @@
1
+ import * as monitorState from "./state.js";
2
+ import { createLogger } from "../utils/logger.js";
3
+ // 只解构 monitorState 的导出
4
+ const { clearDingtalkWebhookRateLimitStateForTest, getDingtalkWebhookRateLimitStateSizeForTest, isWebhookRateLimitedForTest, stopDingtalkMonitorState, } = monitorState;
5
+ export { clearDingtalkWebhookRateLimitStateForTest, getDingtalkWebhookRateLimitStateSizeForTest, isWebhookRateLimitedForTest, } from "./state.js";
6
+ export async function monitorDingtalkProvider(opts = {}) {
7
+ const cfg = opts.config;
8
+ if (!cfg) {
9
+ throw new Error("Config is required for DingTalk monitor");
10
+ }
11
+ const log = createLogger(cfg.channels?.["dingtalk-connector"]?.debug ?? false);
12
+ // 并行导入所有模块(无循环依赖,可以并行)
13
+ const [accountsModule, monitorAccountModule, monitorSingleModule] = await Promise.all([
14
+ import("../config/accounts"),
15
+ import("./message-handler"),
16
+ import("./connection"),
17
+ ]);
18
+ const { resolveDingtalkAccount, listEnabledDingtalkAccounts } = accountsModule;
19
+ const { handleDingTalkMessage } = monitorAccountModule;
20
+ const { monitorSingleAccount, resolveReactionSyntheticEvent } = monitorSingleModule;
21
+ if (opts.accountId) {
22
+ const account = resolveDingtalkAccount({ cfg, accountId: opts.accountId });
23
+ if (!account.enabled || !account.configured) {
24
+ throw new Error(`DingTalk account "${opts.accountId}" not configured or disabled`);
25
+ }
26
+ return monitorSingleAccount({
27
+ cfg,
28
+ account,
29
+ runtime: opts.runtime,
30
+ abortSignal: opts.abortSignal,
31
+ messageHandler: handleDingTalkMessage,
32
+ onStatusChange: opts.onStatusChange,
33
+ });
34
+ }
35
+ const accounts = listEnabledDingtalkAccounts(cfg);
36
+ if (accounts.length === 0) {
37
+ throw new Error("No enabled DingTalk accounts configured");
38
+ }
39
+ log?.info?.(`dingtalk-connector: starting ${accounts.length} account(s): ${accounts.map((a) => a.accountId).join(", ")}`);
40
+ const monitorPromises = [];
41
+ for (const account of accounts) {
42
+ if (opts.abortSignal?.aborted) {
43
+ log?.info?.("dingtalk-connector: abort signal received during startup preflight; stopping startup");
44
+ break;
45
+ }
46
+ monitorPromises.push(monitorSingleAccount({
47
+ cfg,
48
+ account,
49
+ runtime: opts.runtime,
50
+ abortSignal: opts.abortSignal,
51
+ messageHandler: handleDingTalkMessage,
52
+ onStatusChange: opts.onStatusChange,
53
+ }));
54
+ }
55
+ await Promise.all(monitorPromises);
56
+ }
57
+ export function stopDingtalkMonitor(accountId) {
58
+ stopDingtalkMonitorState(accountId);
59
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 钉钉消息流状态管理
3
+ *
4
+ * 职责:
5
+ * - 管理每个钉钉账号的运行状态
6
+ * - 存储 AbortController 用于优雅停止消息流
7
+ * - 提供测试工具函数
8
+ *
9
+ * 核心功能:
10
+ * - setDingtalkMonitorState: 设置账号运行状态
11
+ * - getDingtalkMonitorState: 获取账号运行状态
12
+ * - stopDingtalkMonitorState: 停止单个或多个账号的消息流
13
+ * - 测试工具:clearDingtalkWebhookRateLimitStateForTest 等
14
+ */
15
+ const monitorState = new Map();
16
+ export function setDingtalkMonitorState(accountId, state) {
17
+ monitorState.set(accountId, state);
18
+ }
19
+ export function getDingtalkMonitorState(accountId) {
20
+ return monitorState.get(accountId);
21
+ }
22
+ export function stopDingtalkMonitorState(accountId) {
23
+ if (accountId) {
24
+ const state = monitorState.get(accountId);
25
+ if (state?.abortController) {
26
+ state.abortController.abort();
27
+ }
28
+ monitorState.delete(accountId);
29
+ }
30
+ else {
31
+ // Stop all monitors
32
+ for (const [id, state] of monitorState.entries()) {
33
+ if (state.abortController) {
34
+ state.abortController.abort();
35
+ }
36
+ }
37
+ monitorState.clear();
38
+ }
39
+ }
40
+ // Test utilities
41
+ export function clearDingtalkWebhookRateLimitStateForTest() {
42
+ // DingTalk doesn't use webhook rate limiting
43
+ }
44
+ export function getDingtalkWebhookRateLimitStateSizeForTest() {
45
+ return 0;
46
+ }
47
+ export function isWebhookRateLimitedForTest() {
48
+ return false;
49
+ }
@@ -0,0 +1,53 @@
1
+ import { resolveDingtalkAccount } from "./config/accounts.js";
2
+ import { normalizeDingtalkTarget } from "./targets.js";
3
+ export async function listDingtalkDirectoryPeers(params) {
4
+ const account = resolveDingtalkAccount({ cfg: params.cfg, accountId: params.accountId });
5
+ const dingtalkCfg = account.config;
6
+ const q = params.query?.trim().toLowerCase() || "";
7
+ const ids = new Set();
8
+ for (const entry of dingtalkCfg?.allowFrom ?? []) {
9
+ const trimmed = String(entry).trim();
10
+ if (trimmed && trimmed !== "*") {
11
+ ids.add(trimmed);
12
+ }
13
+ }
14
+ return Array.from(ids)
15
+ .map((raw) => raw.trim())
16
+ .filter(Boolean)
17
+ .map((raw) => normalizeDingtalkTarget(raw) ?? raw)
18
+ .filter((id) => (q ? id.toLowerCase().includes(q) : true))
19
+ .slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
20
+ .map((id) => ({ kind: "user", id }));
21
+ }
22
+ export async function listDingtalkDirectoryGroups(params) {
23
+ const account = resolveDingtalkAccount({ cfg: params.cfg, accountId: params.accountId });
24
+ const dingtalkCfg = account.config;
25
+ const q = params.query?.trim().toLowerCase() || "";
26
+ const ids = new Set();
27
+ for (const groupId of Object.keys(dingtalkCfg?.groups ?? {})) {
28
+ const trimmed = groupId.trim();
29
+ if (trimmed && trimmed !== "*") {
30
+ ids.add(trimmed);
31
+ }
32
+ }
33
+ for (const entry of dingtalkCfg?.groupAllowFrom ?? []) {
34
+ const trimmed = String(entry).trim();
35
+ if (trimmed && trimmed !== "*") {
36
+ ids.add(trimmed);
37
+ }
38
+ }
39
+ return Array.from(ids)
40
+ .map((raw) => raw.trim())
41
+ .filter(Boolean)
42
+ .filter((id) => (q ? id.toLowerCase().includes(q) : true))
43
+ .slice(0, params.limit && params.limit > 0 ? params.limit : undefined)
44
+ .map((id) => ({ kind: "group", id }));
45
+ }
46
+ export async function listDingtalkDirectoryPeersLive(params) {
47
+ // DingTalk doesn't have a public API to list users, so we fall back to static list
48
+ return listDingtalkDirectoryPeers(params);
49
+ }
50
+ export async function listDingtalkDirectoryGroupsLive(params) {
51
+ // DingTalk doesn't have a public API to list groups, so we fall back to static list
52
+ return listDingtalkDirectoryGroups(params);
53
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * 钉钉文档 API 客户端
3
+ * 支持读写钉钉在线文档(文档、表格等)
4
+ */
5
+ import { getAccessToken, DINGTALK_API } from './utils/index.js';
6
+ import { dingtalkHttp } from './utils/http-client.js';
7
+ // ============ 钉钉文档客户端类 ============
8
+ export class DingtalkDocsClient {
9
+ config;
10
+ log;
11
+ constructor(config, log) {
12
+ this.config = config;
13
+ this.log = log;
14
+ }
15
+ /** 获取带鉴权的请求头 */
16
+ async getHeaders() {
17
+ const token = await getAccessToken(this.config);
18
+ return {
19
+ 'x-acs-dingtalk-access-token': token,
20
+ 'Content-Type': 'application/json',
21
+ };
22
+ }
23
+ /**
24
+ * 获取文档元信息
25
+ */
26
+ async getDocInfo(spaceId, docId) {
27
+ try {
28
+ const headers = await this.getHeaders();
29
+ this.log?.info?.(`[DingTalk][Docs] 获取文档信息: spaceId=${spaceId}, docId=${docId}`);
30
+ const resp = await dingtalkHttp.get(`${DINGTALK_API}/v1.0/doc/spaces/${spaceId}/docs/${docId}`, { headers, timeout: 10_000 });
31
+ const data = resp.data;
32
+ this.log?.info?.(`[DingTalk][Docs] 文档信息获取成功: title=${data?.title}`);
33
+ return {
34
+ docId: data.docId || docId,
35
+ title: data.title || '',
36
+ docType: data.docType || 'unknown',
37
+ creatorId: data.creatorId,
38
+ updatedAt: data.updatedAt,
39
+ };
40
+ }
41
+ catch (err) {
42
+ this.log?.error?.(`[DingTalk][Docs] 获取文档信息失败: ${err.message}`);
43
+ return null;
44
+ }
45
+ }
46
+ /**
47
+ * 读取文档内容(通过 v2.0/wiki 节点 API)
48
+ */
49
+ async readDoc(nodeId, operatorId) {
50
+ try {
51
+ const headers = await this.getHeaders();
52
+ this.log?.info?.(`[DingTalk][Docs] 读取知识库节点: nodeId=${nodeId}, operatorId=${operatorId}`);
53
+ if (!operatorId) {
54
+ this.log?.error?.('[DingTalk][Docs] readDoc 需要 operatorId(unionId)');
55
+ return null;
56
+ }
57
+ const resp = await dingtalkHttp.get(`${DINGTALK_API}/v2.0/wiki/nodes/${nodeId}/content`, { headers, params: { operatorId }, timeout: 30_000 });
58
+ const node = resp.data?.node || resp.data;
59
+ const name = node.name || '未知文档';
60
+ const category = node.category || 'unknown';
61
+ const url = node.url || '';
62
+ const workspaceId = node.workspaceId || '';
63
+ const content = [
64
+ `文档名: ${name}`,
65
+ `类型: ${category}`,
66
+ `URL: ${url}`,
67
+ `工作区: ${workspaceId}`,
68
+ ].join('\n');
69
+ this.log?.info?.(`[DingTalk][Docs] 节点信息获取成功: name=${name}, category=${category}`);
70
+ return content;
71
+ }
72
+ catch (err) {
73
+ this.log?.error?.(`[DingTalk][Docs] 读取节点失败: ${err.message}`);
74
+ if (err.response) {
75
+ this.log?.error?.(`[DingTalk][Docs] 错误详情: status=${err.response.status} data=${JSON.stringify(err.response.data)}`);
76
+ }
77
+ return null;
78
+ }
79
+ }
80
+ /**
81
+ * 从 block 树中递归提取纯文本内容
82
+ */
83
+ extractTextFromBlocks(blocks) {
84
+ const result = [];
85
+ for (const block of blocks) {
86
+ if (block.text) {
87
+ result.push(block.text);
88
+ }
89
+ if (block.children && block.children.length > 0) {
90
+ result.push(...this.extractTextFromBlocks(block.children));
91
+ }
92
+ }
93
+ return result;
94
+ }
95
+ /**
96
+ * 向文档追加内容
97
+ */
98
+ async appendToDoc(docId, content, index = -1) {
99
+ try {
100
+ const headers = await this.getHeaders();
101
+ this.log?.info?.(`[DingTalk][Docs] 向文档追加内容: docId=${docId}, contentLen=${content.length}`);
102
+ const body = {
103
+ blockType: 'PARAGRAPH',
104
+ body: {
105
+ text: content,
106
+ },
107
+ index,
108
+ };
109
+ await dingtalkHttp.post(`${DINGTALK_API}/v1.0/doc/documents/${docId}/blocks/root/children`, body, { headers, timeout: 10_000 });
110
+ this.log?.info?.(`[DingTalk][Docs] 内容追加成功`);
111
+ return true;
112
+ }
113
+ catch (err) {
114
+ this.log?.error?.(`[DingTalk][Docs] 追加内容失败: ${err.message}`);
115
+ if (err.response) {
116
+ this.log?.error?.(`[DingTalk][Docs] 错误详情: status=${err.response.status} data=${JSON.stringify(err.response.data)}`);
117
+ }
118
+ return false;
119
+ }
120
+ }
121
+ /**
122
+ * 创建新文档
123
+ */
124
+ async createDoc(spaceId, title, content) {
125
+ try {
126
+ const headers = await this.getHeaders();
127
+ this.log?.info?.(`[DingTalk][Docs] 创建文档: spaceId=${spaceId}, title=${title}`);
128
+ const body = {
129
+ spaceId,
130
+ parentDentryId: '',
131
+ name: title,
132
+ docType: 'alidoc',
133
+ };
134
+ const resp = await dingtalkHttp.post(`${DINGTALK_API}/v1.0/doc/spaces/${spaceId}/docs`, body, { headers, timeout: 10_000 });
135
+ const data = resp.data;
136
+ this.log?.info?.(`[DingTalk][Docs] 文档创建成功: docId=${data?.docId}`);
137
+ const docInfo = {
138
+ docId: data.docId || data.dentryUuid || '',
139
+ title: title,
140
+ docType: data.docType || 'alidoc',
141
+ };
142
+ if (content && docInfo.docId) {
143
+ await this.appendToDoc(docInfo.docId, content);
144
+ }
145
+ return docInfo;
146
+ }
147
+ catch (err) {
148
+ this.log?.error?.(`[DingTalk][Docs] 创建文档失败: ${err.message}`);
149
+ if (err.response) {
150
+ this.log?.error?.(`[DingTalk][Docs] 错误详情: status=${err.response.status} data=${JSON.stringify(err.response.data)}`);
151
+ }
152
+ return null;
153
+ }
154
+ }
155
+ /**
156
+ * 搜索文档
157
+ */
158
+ async searchDocs(keyword, spaceId) {
159
+ try {
160
+ const headers = await this.getHeaders();
161
+ this.log?.info?.(`[DingTalk][Docs] 搜索文档: keyword=${keyword}, spaceId=${spaceId || '全部'}`);
162
+ const body = { keyword, maxResults: 20 };
163
+ if (spaceId)
164
+ body.spaceId = spaceId;
165
+ const resp = await dingtalkHttp.post(`${DINGTALK_API}/v1.0/doc/docs/search`, body, { headers, timeout: 10_000 });
166
+ const items = resp.data?.items || [];
167
+ const docs = items.map((item) => ({
168
+ docId: item.docId || item.dentryUuid || '',
169
+ title: item.name || item.title || '',
170
+ docType: item.docType || 'unknown',
171
+ creatorId: item.creatorId,
172
+ updatedAt: item.updatedAt,
173
+ }));
174
+ this.log?.info?.(`[DingTalk][Docs] 搜索到 ${docs.length} 个文档`);
175
+ return docs;
176
+ }
177
+ catch (err) {
178
+ this.log?.error?.(`[DingTalk][Docs] 搜索文档失败: ${err.message}`);
179
+ return [];
180
+ }
181
+ }
182
+ /**
183
+ * 列出空间下的文档
184
+ */
185
+ async listDocs(spaceId, parentId) {
186
+ try {
187
+ const headers = await this.getHeaders();
188
+ this.log?.info?.(`[DingTalk][Docs] 列出文档: spaceId=${spaceId}, parentId=${parentId || '根目录'}`);
189
+ const params = { maxResults: 50 };
190
+ if (parentId)
191
+ params.parentDentryId = parentId;
192
+ const resp = await dingtalkHttp.get(`${DINGTALK_API}/v1.0/doc/spaces/${spaceId}/dentries`, { headers, params, timeout: 10_000 });
193
+ const items = resp.data?.items || [];
194
+ const docs = items.map((item) => ({
195
+ docId: item.dentryUuid || item.docId || '',
196
+ title: item.name || '',
197
+ docType: item.docType || item.dentryType || 'unknown',
198
+ creatorId: item.creatorId,
199
+ updatedAt: item.updatedAt,
200
+ }));
201
+ this.log?.info?.(`[DingTalk][Docs] 列出 ${docs.length} 个文档/目录`);
202
+ return docs;
203
+ }
204
+ catch (err) {
205
+ this.log?.error?.(`[DingTalk][Docs] 列出文档失败: ${err.message}`);
206
+ return [];
207
+ }
208
+ }
209
+ }