@jeik/dingtalk-connector 0.8.21

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 (154) hide show
  1. package/CHANGELOG.md +684 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +179 -0
  4. package/README.md +219 -0
  5. package/bin/dingtalk-connector.js +838 -0
  6. package/bin/wizard-config.mjs +94 -0
  7. package/dist/accounts-BAzdqkAV.mjs +268 -0
  8. package/dist/accounts-BQptOmgB.mjs +2 -0
  9. package/dist/chunk-upload-BBQgGtcZ.mjs +193 -0
  10. package/dist/chunk-upload-DaLXXZH3.mjs +2 -0
  11. package/dist/common-C8pYKU_y.mjs +2 -0
  12. package/dist/common-Dt9n6fQN.mjs +101 -0
  13. package/dist/connection-DHHFFNQJ.mjs +423 -0
  14. package/dist/entry-bundled.d.mts +16 -0
  15. package/dist/entry-bundled.mjs +31 -0
  16. package/dist/game-xiyou-CqHt-6Q1.mjs +4271 -0
  17. package/dist/gateway-methods-C4tcgI7P.mjs +771 -0
  18. package/dist/gateway-methods-Ci31A3vg.mjs +2 -0
  19. package/dist/http-client-CpnJHB89.mjs +2 -0
  20. package/dist/http-client-DFWZgO1n.mjs +33 -0
  21. package/dist/index.d.mts +193 -0
  22. package/dist/index.mjs +45 -0
  23. package/dist/logger-BmJkQkm1.mjs +2 -0
  24. package/dist/logger-mZ9OSbmD.mjs +58 -0
  25. package/dist/media-C_SVin7s.mjs +2 -0
  26. package/dist/media-cz72EVS3.mjs +509 -0
  27. package/dist/message-handler-DESzFFDc.mjs +1971 -0
  28. package/dist/messaging-B6l1sRvX.mjs +1044 -0
  29. package/dist/runtime-DUgpo5zC.mjs +1422 -0
  30. package/dist/session-DJ4jYqPv.mjs +114 -0
  31. package/dist/utils-Bjh4r_qS.mjs +4 -0
  32. package/dist/utils-CIfI_3Jh.mjs +63 -0
  33. package/dist/utils-legacy-CALCPP1t.mjs +230 -0
  34. package/dist/utils-legacy-CFYDBM4r.mjs +3 -0
  35. package/docs/DEAP_AGENT_GUIDE.en.md +115 -0
  36. package/docs/DEAP_AGENT_GUIDE.md +115 -0
  37. package/docs/DINGTALK_MANUAL_SETUP.md +50 -0
  38. package/docs/MULTI_AGENT_SETUP.md +306 -0
  39. package/docs/RELEASE_NOTES_V0.7.10.md +40 -0
  40. package/docs/RELEASE_NOTES_V0.7.2.md +143 -0
  41. package/docs/RELEASE_NOTES_V0.7.3.md +149 -0
  42. package/docs/RELEASE_NOTES_V0.7.4.md +206 -0
  43. package/docs/RELEASE_NOTES_V0.7.5.md +267 -0
  44. package/docs/RELEASE_NOTES_V0.7.6.md +219 -0
  45. package/docs/RELEASE_NOTES_V0.7.7.md +122 -0
  46. package/docs/RELEASE_NOTES_V0.7.8.md +101 -0
  47. package/docs/RELEASE_NOTES_V0.7.9.md +65 -0
  48. package/docs/RELEASE_NOTES_V0.8.0.md +53 -0
  49. package/docs/RELEASE_NOTES_V0.8.1.md +47 -0
  50. package/docs/RELEASE_NOTES_V0.8.10.md +49 -0
  51. package/docs/RELEASE_NOTES_V0.8.11.md +51 -0
  52. package/docs/RELEASE_NOTES_V0.8.12.md +63 -0
  53. package/docs/RELEASE_NOTES_V0.8.13-beta.0.md +69 -0
  54. package/docs/RELEASE_NOTES_V0.8.13.md +62 -0
  55. package/docs/RELEASE_NOTES_V0.8.14.md +86 -0
  56. package/docs/RELEASE_NOTES_V0.8.16.md +40 -0
  57. package/docs/RELEASE_NOTES_V0.8.17.md +87 -0
  58. package/docs/RELEASE_NOTES_V0.8.18.md +64 -0
  59. package/docs/RELEASE_NOTES_V0.8.19.md +62 -0
  60. package/docs/RELEASE_NOTES_V0.8.2.md +55 -0
  61. package/docs/RELEASE_NOTES_V0.8.20.md +49 -0
  62. package/docs/RELEASE_NOTES_V0.8.3.md +63 -0
  63. package/docs/RELEASE_NOTES_V0.8.4.md +45 -0
  64. package/docs/RELEASE_NOTES_V0.8.7.md +49 -0
  65. package/docs/RELEASE_NOTES_V0.8.8.md +63 -0
  66. package/docs/RELEASE_NOTES_V0.8.9.md +81 -0
  67. package/docs/RELEASE_NOTES_v0.7.0.md +142 -0
  68. package/docs/RELEASE_NOTES_v0.7.1.md +74 -0
  69. package/docs/TROUBLESHOOTING.md +122 -0
  70. package/index.ts +77 -0
  71. package/openclaw.plugin.json +551 -0
  72. package/package.json +147 -0
  73. package/skills/dingtalk-channel-rules/SKILL.md +91 -0
  74. package/skills/dingtalk-troubleshoot/SKILL.md +93 -0
  75. package/skills/dws-cli/SKILL.md +129 -0
  76. package/skills/dws-cli/references/error-codes.md +95 -0
  77. package/skills/dws-cli/references/field-rules.md +105 -0
  78. package/skills/dws-cli/references/global-reference.md +104 -0
  79. package/skills/dws-cli/references/intent-guide.md +114 -0
  80. package/skills/dws-cli/references/products/aitable.md +452 -0
  81. package/skills/dws-cli/references/products/attendance.md +93 -0
  82. package/skills/dws-cli/references/products/calendar.md +217 -0
  83. package/skills/dws-cli/references/products/chat.md +292 -0
  84. package/skills/dws-cli/references/products/contact.md +108 -0
  85. package/skills/dws-cli/references/products/ding.md +57 -0
  86. package/skills/dws-cli/references/products/report.md +162 -0
  87. package/skills/dws-cli/references/products/simple.md +128 -0
  88. package/skills/dws-cli/references/products/todo.md +138 -0
  89. package/skills/dws-cli/references/products/workbench.md +39 -0
  90. package/skills/dws-cli/references/recovery-guide.md +94 -0
  91. package/src/channel.ts +588 -0
  92. package/src/config/accounts.ts +242 -0
  93. package/src/config/schema.ts +180 -0
  94. package/src/core/connection.ts +741 -0
  95. package/src/core/message-handler.ts +1788 -0
  96. package/src/core/provider.ts +111 -0
  97. package/src/core/state.ts +54 -0
  98. package/src/device-auth-config.ts +14 -0
  99. package/src/device-auth.ts +197 -0
  100. package/src/directory.ts +95 -0
  101. package/src/docs.ts +293 -0
  102. package/src/game-xiyou/achievement-engine.ts +252 -0
  103. package/src/game-xiyou/bounty-system.ts +315 -0
  104. package/src/game-xiyou/commands.ts +223 -0
  105. package/src/game-xiyou/drop-engine.ts +241 -0
  106. package/src/game-xiyou/encounter-system.ts +135 -0
  107. package/src/game-xiyou/escape-engine.ts +164 -0
  108. package/src/game-xiyou/exp-calculator.ts +139 -0
  109. package/src/game-xiyou/index.ts +479 -0
  110. package/src/game-xiyou/level-system.ts +91 -0
  111. package/src/game-xiyou/monster-pool.ts +180 -0
  112. package/src/game-xiyou/pity-counter.ts +114 -0
  113. package/src/game-xiyou/random-event-engine.ts +648 -0
  114. package/src/game-xiyou/renderer.ts +679 -0
  115. package/src/game-xiyou/storage.ts +218 -0
  116. package/src/game-xiyou/treasure-system.ts +105 -0
  117. package/src/game-xiyou/types.ts +582 -0
  118. package/src/game-xiyou/uid-resolver.ts +49 -0
  119. package/src/gateway-methods.ts +740 -0
  120. package/src/onboarding.ts +553 -0
  121. package/src/policy.ts +32 -0
  122. package/src/probe.ts +210 -0
  123. package/src/reply-dispatcher.ts +874 -0
  124. package/src/runtime.ts +32 -0
  125. package/src/sdk/helpers.ts +322 -0
  126. package/src/sdk/types.ts +519 -0
  127. package/src/secret-input.ts +19 -0
  128. package/src/services/media/audio.ts +54 -0
  129. package/src/services/media/chunk-upload.ts +296 -0
  130. package/src/services/media/common.ts +155 -0
  131. package/src/services/media/file.ts +75 -0
  132. package/src/services/media/image.ts +81 -0
  133. package/src/services/media/index.ts +10 -0
  134. package/src/services/media/video.ts +162 -0
  135. package/src/services/media.ts +1143 -0
  136. package/src/services/messaging/card.ts +604 -0
  137. package/src/services/messaging/index.ts +18 -0
  138. package/src/services/messaging/mentions.ts +267 -0
  139. package/src/services/messaging/send.ts +141 -0
  140. package/src/services/messaging.ts +1191 -0
  141. package/src/services/reply-markers.ts +55 -0
  142. package/src/targets.ts +45 -0
  143. package/src/types/index.ts +59 -0
  144. package/src/types/pdf-parse.d.ts +3 -0
  145. package/src/utils/agent.ts +63 -0
  146. package/src/utils/async.ts +51 -0
  147. package/src/utils/constants.ts +27 -0
  148. package/src/utils/http-client.ts +38 -0
  149. package/src/utils/index.ts +8 -0
  150. package/src/utils/logger.ts +78 -0
  151. package/src/utils/session.ts +147 -0
  152. package/src/utils/token.ts +93 -0
  153. package/src/utils/utils-legacy.ts +454 -0
  154. package/tsconfig.json +20 -0
@@ -0,0 +1,771 @@
1
+ import { a as resolveDingtalkAccount, t as listDingtalkAccountIds } from "./accounts-BAzdqkAV.mjs";
2
+ import { t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
3
+ import { r as getAccessToken, t as DINGTALK_API } from "./utils-CIfI_3Jh.mjs";
4
+ import { c as prepareMultiBotMentions, i as sendProactive, s as buildBotMentionTable, u as finishAICard } from "./messaging-B6l1sRvX.mjs";
5
+ import { c as getUnionId, d as recallEmotionReply } from "./utils-legacy-CALCPP1t.mjs";
6
+ //#region src/docs.ts
7
+ var DingtalkDocsClient = class {
8
+ config;
9
+ log;
10
+ constructor(config, log) {
11
+ this.config = config;
12
+ this.log = log;
13
+ }
14
+ /** 获取带鉴权的请求头 */
15
+ async getHeaders() {
16
+ return {
17
+ "x-acs-dingtalk-access-token": await getAccessToken(this.config),
18
+ "Content-Type": "application/json"
19
+ };
20
+ }
21
+ /**
22
+ * 获取文档元信息
23
+ */
24
+ async getDocInfo(spaceId, docId) {
25
+ try {
26
+ const headers = await this.getHeaders();
27
+ this.log?.info?.(`[DingTalk][Docs] 获取文档信息: spaceId=${spaceId}, docId=${docId}`);
28
+ const data = (await dingtalkHttp.get(`${DINGTALK_API}/v1.0/doc/spaces/${spaceId}/docs/${docId}`, {
29
+ headers,
30
+ timeout: 1e4
31
+ })).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
+ } catch (err) {
41
+ this.log?.error?.(`[DingTalk][Docs] 获取文档信息失败: ${err.message}`);
42
+ return null;
43
+ }
44
+ }
45
+ /**
46
+ * 读取文档内容(通过 v2.0/wiki 节点 API)
47
+ */
48
+ async readDoc(nodeId, operatorId) {
49
+ try {
50
+ const headers = await this.getHeaders();
51
+ this.log?.info?.(`[DingTalk][Docs] 读取知识库节点: nodeId=${nodeId}, operatorId=${operatorId}`);
52
+ if (!operatorId) {
53
+ this.log?.error?.("[DingTalk][Docs] readDoc 需要 operatorId(unionId)");
54
+ return null;
55
+ }
56
+ const resp = await dingtalkHttp.get(`${DINGTALK_API}/v2.0/wiki/nodes/${nodeId}/content`, {
57
+ headers,
58
+ params: { operatorId },
59
+ timeout: 3e4
60
+ });
61
+ const node = resp.data?.node || resp.data;
62
+ const name = node.name || "未知文档";
63
+ const category = node.category || "unknown";
64
+ const url = node.url || "";
65
+ const workspaceId = node.workspaceId || "";
66
+ const content = [
67
+ `文档名: ${name}`,
68
+ `类型: ${category}`,
69
+ `URL: ${url}`,
70
+ `工作区: ${workspaceId}`
71
+ ].join("\n");
72
+ this.log?.info?.(`[DingTalk][Docs] 节点信息获取成功: name=${name}, category=${category}`);
73
+ return content;
74
+ } catch (err) {
75
+ this.log?.error?.(`[DingTalk][Docs] 读取节点失败: ${err.message}`);
76
+ if (err.response) this.log?.error?.(`[DingTalk][Docs] 错误详情: status=${err.response.status} data=${JSON.stringify(err.response.data)}`);
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) result.push(block.text);
87
+ if (block.children && block.children.length > 0) result.push(...this.extractTextFromBlocks(block.children));
88
+ }
89
+ return result;
90
+ }
91
+ /**
92
+ * 向文档追加内容
93
+ */
94
+ async appendToDoc(docId, content, index = -1) {
95
+ try {
96
+ const headers = await this.getHeaders();
97
+ this.log?.info?.(`[DingTalk][Docs] 向文档追加内容: docId=${docId}, contentLen=${content.length}`);
98
+ const body = {
99
+ blockType: "PARAGRAPH",
100
+ body: { text: content },
101
+ index
102
+ };
103
+ await dingtalkHttp.post(`${DINGTALK_API}/v1.0/doc/documents/${docId}/blocks/root/children`, body, {
104
+ headers,
105
+ timeout: 1e4
106
+ });
107
+ this.log?.info?.(`[DingTalk][Docs] 内容追加成功`);
108
+ return true;
109
+ } catch (err) {
110
+ this.log?.error?.(`[DingTalk][Docs] 追加内容失败: ${err.message}`);
111
+ if (err.response) this.log?.error?.(`[DingTalk][Docs] 错误详情: status=${err.response.status} data=${JSON.stringify(err.response.data)}`);
112
+ return false;
113
+ }
114
+ }
115
+ /**
116
+ * 创建新文档
117
+ */
118
+ async createDoc(spaceId, title, content) {
119
+ try {
120
+ const headers = await this.getHeaders();
121
+ this.log?.info?.(`[DingTalk][Docs] 创建文档: spaceId=${spaceId}, title=${title}`);
122
+ const body = {
123
+ spaceId,
124
+ parentDentryId: "",
125
+ name: title,
126
+ docType: "alidoc"
127
+ };
128
+ const data = (await dingtalkHttp.post(`${DINGTALK_API}/v1.0/doc/spaces/${spaceId}/docs`, body, {
129
+ headers,
130
+ timeout: 1e4
131
+ })).data;
132
+ this.log?.info?.(`[DingTalk][Docs] 文档创建成功: docId=${data?.docId}`);
133
+ const docInfo = {
134
+ docId: data.docId || data.dentryUuid || "",
135
+ title,
136
+ docType: data.docType || "alidoc"
137
+ };
138
+ if (content && docInfo.docId) await this.appendToDoc(docInfo.docId, content);
139
+ return docInfo;
140
+ } catch (err) {
141
+ this.log?.error?.(`[DingTalk][Docs] 创建文档失败: ${err.message}`);
142
+ if (err.response) this.log?.error?.(`[DingTalk][Docs] 错误详情: status=${err.response.status} data=${JSON.stringify(err.response.data)}`);
143
+ return null;
144
+ }
145
+ }
146
+ /**
147
+ * 搜索文档
148
+ */
149
+ async searchDocs(keyword, spaceId) {
150
+ try {
151
+ const headers = await this.getHeaders();
152
+ this.log?.info?.(`[DingTalk][Docs] 搜索文档: keyword=${keyword}, spaceId=${spaceId || "全部"}`);
153
+ const body = {
154
+ keyword,
155
+ maxResults: 20
156
+ };
157
+ if (spaceId) body.spaceId = spaceId;
158
+ const docs = ((await dingtalkHttp.post(`https://api.dingtalk.com/v1.0/doc/docs/search`, body, {
159
+ headers,
160
+ timeout: 1e4
161
+ })).data?.items || []).map((item) => ({
162
+ docId: item.docId || item.dentryUuid || "",
163
+ title: item.name || item.title || "",
164
+ docType: item.docType || "unknown",
165
+ creatorId: item.creatorId,
166
+ updatedAt: item.updatedAt
167
+ }));
168
+ this.log?.info?.(`[DingTalk][Docs] 搜索到 ${docs.length} 个文档`);
169
+ return docs;
170
+ } catch (err) {
171
+ this.log?.error?.(`[DingTalk][Docs] 搜索文档失败: ${err.message}`);
172
+ return [];
173
+ }
174
+ }
175
+ /**
176
+ * 列出空间下的文档
177
+ */
178
+ async listDocs(spaceId, parentId) {
179
+ try {
180
+ const headers = await this.getHeaders();
181
+ this.log?.info?.(`[DingTalk][Docs] 列出文档: spaceId=${spaceId}, parentId=${parentId || "根目录"}`);
182
+ const params = { maxResults: 50 };
183
+ if (parentId) params.parentDentryId = parentId;
184
+ const docs = ((await dingtalkHttp.get(`https://api.dingtalk.com/v1.0/doc/spaces/${spaceId}/dentries`, {
185
+ headers,
186
+ params,
187
+ timeout: 1e4
188
+ })).data?.items || []).map((item) => ({
189
+ docId: item.dentryUuid || item.docId || "",
190
+ title: item.name || "",
191
+ docType: item.docType || item.dentryType || "unknown",
192
+ creatorId: item.creatorId,
193
+ updatedAt: item.updatedAt
194
+ }));
195
+ this.log?.info?.(`[DingTalk][Docs] 列出 ${docs.length} 个文档/目录`);
196
+ return docs;
197
+ } catch (err) {
198
+ this.log?.error?.(`[DingTalk][Docs] 列出文档失败: ${err.message}`);
199
+ return [];
200
+ }
201
+ }
202
+ };
203
+ //#endregion
204
+ //#region src/gateway-methods.ts
205
+ /**
206
+ * Warn when accountId is not explicitly provided and multiple accounts exist.
207
+ * Returns the resolved account (unchanged), but emits a log warning so that
208
+ * callers (typically AI agents) learn to pass accountId explicitly.
209
+ */
210
+ function warnIfAccountIdMissing(cfg, accountId, method, log) {
211
+ if (accountId) return;
212
+ const allIds = listDingtalkAccountIds(cfg);
213
+ if (allIds.length > 1) log?.warn?.(`[Gateway][${method}] accountId not specified but ${allIds.length} accounts configured (${allIds.join(", ")}). Falling back to default account. To use the correct bot, pass accountId explicitly.`);
214
+ }
215
+ /**
216
+ * 注册所有 Gateway Methods
217
+ */
218
+ function registerGatewayMethods(api) {
219
+ const log = api.logger;
220
+ /**
221
+ * 主动发送单聊消息
222
+ *
223
+ * @example
224
+ * ```typescript
225
+ * await gateway.call('dingtalk-connector.sendToUser', {
226
+ * userId: 'user123',
227
+ * content: '任务已完成!',
228
+ * useAICard: true
229
+ * });
230
+ * ```
231
+ */
232
+ api.registerGatewayMethod("dingtalk-connector.sendToUser", async ({ context, params, respond }) => {
233
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
234
+ const cfg = loadConfig();
235
+ try {
236
+ const { userId, userIds, content, msgType, title, useAICard, fallbackToNormal, accountId, atDingtalkIds, atUserIds, atAccountIds, atAll } = params || {};
237
+ warnIfAccountIdMissing(cfg, accountId, "sendToUser", log);
238
+ const account = resolveDingtalkAccount({
239
+ cfg,
240
+ accountId
241
+ });
242
+ if (!account.config?.clientId) return respond(false, { error: "DingTalk not configured" });
243
+ const targetUserIds = userIds || (userId ? [userId] : []);
244
+ if (targetUserIds.length === 0) return respond(false, { error: "userId or userIds is required" });
245
+ if (!content) return respond(false, { error: "content is required" });
246
+ const target = targetUserIds.length === 1 ? { userId: targetUserIds[0] } : { userIds: targetUserIds };
247
+ const prepared = prepareMultiBotMentions({
248
+ cfg,
249
+ content: String(content),
250
+ atAccountIds,
251
+ atDingtalkIds
252
+ });
253
+ if (prepared.missingAccountIds.length > 0) log?.warn?.(`[Gateway][sendToUser] atAccountIds 未配置 chatbotUserId,已跳过: ${prepared.missingAccountIds.join(", ")}`);
254
+ const result = await sendProactive(account.config, target, prepared.content, {
255
+ msgType,
256
+ title,
257
+ log,
258
+ useAICard: useAICard !== false,
259
+ fallbackToNormal: fallbackToNormal !== false,
260
+ atDingtalkIds: prepared.atDingtalkIds,
261
+ atUserIds,
262
+ atAll
263
+ });
264
+ respond(result.ok, {
265
+ ...result,
266
+ usedAccountId: account.accountId,
267
+ resolvedAtDingtalkIds: prepared.atDingtalkIds,
268
+ missingAtAccountIds: prepared.missingAccountIds
269
+ });
270
+ } catch (err) {
271
+ log?.error?.(`[Gateway][sendToUser] 错误: ${err.message}`);
272
+ respond(false, { error: err.message });
273
+ }
274
+ });
275
+ /**
276
+ * 主动发送群聊消息
277
+ *
278
+ * @example
279
+ * ```typescript
280
+ * await gateway.call('dingtalk-connector.sendToGroup', {
281
+ * openConversationId: 'cid123',
282
+ * content: '构建失败,请检查日志',
283
+ * useAICard: true
284
+ * });
285
+ * ```
286
+ */
287
+ api.registerGatewayMethod("dingtalk-connector.sendToGroup", async ({ context, params, respond }) => {
288
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
289
+ const cfg = loadConfig();
290
+ try {
291
+ const { openConversationId, content, msgType, title, useAICard, fallbackToNormal, accountId, atDingtalkIds, atUserIds, atAccountIds, atAll } = params || {};
292
+ warnIfAccountIdMissing(cfg, accountId, "sendToGroup", log);
293
+ const account = resolveDingtalkAccount({
294
+ cfg,
295
+ accountId
296
+ });
297
+ if (!account.config?.clientId) return respond(false, { error: "DingTalk not configured" });
298
+ if (!openConversationId) return respond(false, { error: "openConversationId is required" });
299
+ if (!content) return respond(false, { error: "content is required" });
300
+ const prepared = prepareMultiBotMentions({
301
+ cfg,
302
+ content: String(content),
303
+ atAccountIds,
304
+ atDingtalkIds
305
+ });
306
+ if (prepared.missingAccountIds.length > 0) log?.warn?.(`[Gateway][sendToGroup] atAccountIds 未配置 chatbotUserId,已跳过: ${prepared.missingAccountIds.join(", ")}。请让该 bot 先收一条消息,抓 [BotIdentity] 日志后回填 accounts.<id>.chatbotUserId`);
307
+ const result = await sendProactive(account.config, { openConversationId }, prepared.content, {
308
+ msgType,
309
+ title,
310
+ log,
311
+ useAICard: useAICard !== false,
312
+ fallbackToNormal: fallbackToNormal !== false,
313
+ atDingtalkIds: prepared.atDingtalkIds,
314
+ atUserIds,
315
+ atAll
316
+ });
317
+ respond(result.ok, {
318
+ ...result,
319
+ usedAccountId: account.accountId,
320
+ resolvedAtDingtalkIds: prepared.atDingtalkIds,
321
+ missingAtAccountIds: prepared.missingAccountIds
322
+ });
323
+ } catch (err) {
324
+ log?.error?.(`[Gateway][sendToGroup] 错误: ${err.message}`);
325
+ console.error(err);
326
+ respond(false, { error: err.message });
327
+ }
328
+ });
329
+ api.registerGatewayMethod("dingtalk-connector.send", async ({ context, params, respond }) => {
330
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
331
+ const cfg = loadConfig();
332
+ try {
333
+ const { target, content, message, msgType, title, useAICard, fallbackToNormal, accountId, atDingtalkIds, atUserIds, atAccountIds, atAll } = params || {};
334
+ const actualContent = content || message;
335
+ warnIfAccountIdMissing(cfg, accountId, "send", log);
336
+ const account = resolveDingtalkAccount({
337
+ cfg,
338
+ accountId
339
+ });
340
+ log?.info?.(`[Gateway][send] 收到请求: target=${target}, contentLen=${typeof actualContent === "string" ? actualContent.length : 0}, accountId=${account.accountId}`);
341
+ if (!account.config?.clientId) return respond(false, { error: "DingTalk not configured" });
342
+ if (!target) return respond(false, { error: "target is required (format: user:<userId> or group:<openConversationId>)" });
343
+ if (!actualContent) return respond(false, { error: "content is required" });
344
+ const targetStr = String(target);
345
+ let sendTarget;
346
+ if (targetStr.startsWith("user:")) sendTarget = { userId: targetStr.slice(5) };
347
+ else if (targetStr.startsWith("group:")) sendTarget = { openConversationId: targetStr.slice(6) };
348
+ else sendTarget = { userId: targetStr };
349
+ const prepared = prepareMultiBotMentions({
350
+ cfg,
351
+ content: String(actualContent),
352
+ atAccountIds,
353
+ atDingtalkIds
354
+ });
355
+ if (prepared.missingAccountIds.length > 0) log?.warn?.(`[Gateway][send] atAccountIds 未配置 chatbotUserId,已跳过: ${prepared.missingAccountIds.join(", ")}`);
356
+ const result = await sendProactive(account.config, sendTarget, prepared.content, {
357
+ msgType,
358
+ title,
359
+ log,
360
+ useAICard: useAICard !== false,
361
+ fallbackToNormal: fallbackToNormal !== false,
362
+ atDingtalkIds: prepared.atDingtalkIds,
363
+ atUserIds,
364
+ atAll
365
+ });
366
+ respond(result.ok, {
367
+ ...result,
368
+ usedAccountId: account.accountId,
369
+ resolvedAtDingtalkIds: prepared.atDingtalkIds,
370
+ missingAtAccountIds: prepared.missingAccountIds
371
+ });
372
+ } catch (err) {
373
+ log?.error?.(`[Gateway][send] 错误: ${err.message}`);
374
+ respond(false, { error: err.message });
375
+ }
376
+ });
377
+ api.registerGatewayMethod("dingtalk-connector.docs.read", async ({ context, params, respond }) => {
378
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
379
+ const cfg = loadConfig();
380
+ try {
381
+ const { docId, operatorId: rawOperatorId, accountId } = params || {};
382
+ const account = resolveDingtalkAccount({
383
+ cfg,
384
+ accountId
385
+ });
386
+ if (!account.config?.clientId) return respond(false, { error: "DingTalk not configured" });
387
+ if (!docId) return respond(false, { error: "docId is required" });
388
+ if (!rawOperatorId) return respond(false, { error: "operatorId (unionId or staffId) is required" });
389
+ let operatorId = rawOperatorId;
390
+ if (!rawOperatorId.includes("$")) {
391
+ const resolved = await getUnionId(rawOperatorId, account.config, log);
392
+ if (resolved) operatorId = resolved;
393
+ }
394
+ const content = await new DingtalkDocsClient(account.config, log).readDoc(docId, operatorId);
395
+ if (content !== null) respond(true, { content });
396
+ else respond(false, { error: "Failed to read document node" });
397
+ } catch (err) {
398
+ log?.error?.(`[Gateway][docs.read] 错误: ${err.message}`);
399
+ respond(false, { error: err.message });
400
+ }
401
+ });
402
+ /**
403
+ * 创建钉钉文档
404
+ *
405
+ * @example
406
+ * ```typescript
407
+ * const result = await gateway.call('dingtalk-connector.docs.create', {
408
+ * spaceId: 'workspace123',
409
+ * title: '会议纪要',
410
+ * content: '今天讨论了...'
411
+ * });
412
+ * console.log('文档ID:', result.docId);
413
+ * ```
414
+ */
415
+ api.registerGatewayMethod("dingtalk-connector.docs.create", async ({ context, params, respond }) => {
416
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
417
+ const cfg = loadConfig();
418
+ try {
419
+ const { spaceId, title, content, accountId } = params || {};
420
+ const account = resolveDingtalkAccount({
421
+ cfg,
422
+ accountId
423
+ });
424
+ if (!account.config?.clientId) return respond(false, { error: "DingTalk not configured" });
425
+ if (!spaceId || !title) return respond(false, { error: "spaceId and title are required" });
426
+ const doc = await new DingtalkDocsClient(account.config, log).createDoc(spaceId, title, content);
427
+ if (doc) respond(true, doc);
428
+ else respond(false, { error: "Failed to create document" });
429
+ } catch (err) {
430
+ log?.error?.(`[Gateway][docs.create] 错误: ${err.message}`);
431
+ respond(false, { error: err.message });
432
+ }
433
+ });
434
+ /**
435
+ * 向钉钉文档追加内容
436
+ *
437
+ * @example
438
+ * ```typescript
439
+ * await gateway.call('dingtalk-connector.docs.append', {
440
+ * docId: 'doc123',
441
+ * content: '补充内容...'
442
+ * });
443
+ * ```
444
+ */
445
+ api.registerGatewayMethod("dingtalk-connector.docs.append", async ({ context, params, respond }) => {
446
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
447
+ const cfg = loadConfig();
448
+ try {
449
+ const { docId, content, accountId } = params || {};
450
+ const account = resolveDingtalkAccount({
451
+ cfg,
452
+ accountId
453
+ });
454
+ if (!account.config?.clientId) return respond(false, { error: "DingTalk not configured" });
455
+ if (!docId || !content) return respond(false, { error: "docId and content are required" });
456
+ const ok = await new DingtalkDocsClient(account.config, log).appendToDoc(docId, content);
457
+ respond(ok, ok ? { success: true } : { error: "Failed to append to document" });
458
+ } catch (err) {
459
+ log?.error?.(`[Gateway][docs.append] 错误: ${err.message}`);
460
+ respond(false, { error: err.message });
461
+ }
462
+ });
463
+ /**
464
+ * 搜索钉钉文档
465
+ *
466
+ * @example
467
+ * ```typescript
468
+ * const result = await gateway.call('dingtalk-connector.docs.search', {
469
+ * keyword: '项目规范',
470
+ * spaceId: 'workspace123' // 可选
471
+ * });
472
+ * console.log('找到文档:', result.docs);
473
+ * ```
474
+ */
475
+ api.registerGatewayMethod("dingtalk-connector.docs.search", async ({ context, params, respond }) => {
476
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
477
+ const cfg = loadConfig();
478
+ try {
479
+ const { keyword, spaceId, accountId } = params || {};
480
+ const account = resolveDingtalkAccount({
481
+ cfg,
482
+ accountId
483
+ });
484
+ if (!account.config?.clientId) return respond(false, { error: "DingTalk not configured" });
485
+ if (!keyword) return respond(false, { error: "keyword is required" });
486
+ respond(true, { docs: await new DingtalkDocsClient(account.config, log).searchDocs(keyword, spaceId) });
487
+ } catch (err) {
488
+ log?.error?.(`[Gateway][docs.search] 错误: ${err.message}`);
489
+ respond(false, { error: err.message });
490
+ }
491
+ });
492
+ /**
493
+ * 列出空间下的文档
494
+ *
495
+ * @example
496
+ * ```typescript
497
+ * const result = await gateway.call('dingtalk-connector.docs.list', {
498
+ * spaceId: 'workspace123',
499
+ * parentId: 'folder456' // 可选,不传则列出根目录
500
+ * });
501
+ * console.log('文档列表:', result.docs);
502
+ * ```
503
+ */
504
+ api.registerGatewayMethod("dingtalk-connector.docs.list", async ({ context, params, respond }) => {
505
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
506
+ const cfg = loadConfig();
507
+ try {
508
+ const { spaceId, parentId, accountId } = params || {};
509
+ const account = resolveDingtalkAccount({
510
+ cfg,
511
+ accountId
512
+ });
513
+ if (!account.config?.clientId) return respond(false, { error: "DingTalk not configured" });
514
+ if (!spaceId) return respond(false, { error: "spaceId is required" });
515
+ respond(true, { docs: await new DingtalkDocsClient(account.config, log).listDocs(spaceId, parentId) });
516
+ } catch (err) {
517
+ log?.error?.(`[Gateway][docs.list] 错误: ${err.message}`);
518
+ respond(false, { error: err.message });
519
+ }
520
+ });
521
+ api.registerGatewayMethod("dingtalk-connector.status", async ({ context, params, respond }) => {
522
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
523
+ const cfg = loadConfig();
524
+ try {
525
+ const accountId = params?.accountId;
526
+ const account = resolveDingtalkAccount({
527
+ cfg,
528
+ accountId
529
+ });
530
+ const hasClientId = !!account.config?.clientId;
531
+ const hasClientSecret = !!account.config?.clientSecret;
532
+ respond(true, {
533
+ configured: hasClientId && hasClientSecret,
534
+ enabled: account.enabled,
535
+ accountId: account.accountId,
536
+ clientId: hasClientId ? String(account.config.clientId).substring(0, 8) + "..." : void 0
537
+ });
538
+ } catch (err) {
539
+ log?.error?.(`[Gateway][status] 错误: ${err.message}`);
540
+ respond(false, { error: err.message });
541
+ }
542
+ });
543
+ /**
544
+ * 修复卡住的 AI Card 和/或残留的🤔表情标签
545
+ *
546
+ * 使用场景:Gateway 重启导致流式响应中断,AI Card 停留在"思考中"状态,
547
+ * 或用户消息上的🤔表情标签未被自动撤回。
548
+ *
549
+ * @example 修复卡住的 AI Card
550
+ * ```typescript
551
+ * await gateway.call('dingtalk-connector.fixStuckCards', {
552
+ * cardInstanceId: 'card_1713600000000_abc12345',
553
+ * content: '(回复中断,请重新提问)'
554
+ * });
555
+ * ```
556
+ *
557
+ * @example 撤回残留的🤔表情
558
+ * ```typescript
559
+ * await gateway.call('dingtalk-connector.fixStuckCards', {
560
+ * msgId: 'msgXXX',
561
+ * conversationId: 'cidXXX'
562
+ * });
563
+ * ```
564
+ *
565
+ * @example 同时修复两者
566
+ * ```typescript
567
+ * await gateway.call('dingtalk-connector.fixStuckCards', {
568
+ * cardInstanceId: 'card_1713600000000_abc12345',
569
+ * msgId: 'msgXXX',
570
+ * conversationId: 'cidXXX'
571
+ * });
572
+ * ```
573
+ */
574
+ api.registerGatewayMethod("dingtalk-connector.fixStuckCards", async ({ context, params, respond }) => {
575
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
576
+ const cfg = loadConfig();
577
+ try {
578
+ const { cardInstanceId, content, msgId, conversationId, accountId } = params || {};
579
+ const account = resolveDingtalkAccount({
580
+ cfg,
581
+ accountId
582
+ });
583
+ if (!account.config?.clientId) return respond(false, { error: "DingTalk not configured" });
584
+ if (!cardInstanceId && !msgId) return respond(false, {
585
+ error: "At least one of cardInstanceId or msgId is required",
586
+ usage: {
587
+ cardInstanceId: "(optional) AI Card outTrackId, found in logs like \"outTrackId=card_...\"",
588
+ content: "(optional) Final card content, defaults to \"(回复中断,请重新提问)\"",
589
+ msgId: "(optional) Message ID for emotion recall, found in logs like \"msgId=...\"",
590
+ conversationId: "(optional) Required together with msgId for emotion recall"
591
+ }
592
+ });
593
+ const results = {};
594
+ if (cardInstanceId) try {
595
+ const { getAccessToken } = await import("./utils-legacy-CFYDBM4r.mjs");
596
+ const token = await getAccessToken(account.config);
597
+ await finishAICard({
598
+ cardInstanceId: String(cardInstanceId),
599
+ accessToken: token,
600
+ tokenExpireTime: Date.now() + 7200 * 1e3,
601
+ inputingStarted: true
602
+ }, String(content || "(回复中断,请重新提问)"), account.config, log);
603
+ results.card = { ok: true };
604
+ log?.info?.(`[Gateway][fixStuckCards] AI Card 修复成功: ${cardInstanceId}`);
605
+ } catch (err) {
606
+ results.card = {
607
+ ok: false,
608
+ error: err.message
609
+ };
610
+ log?.error?.(`[Gateway][fixStuckCards] AI Card 修复失败: ${err.message}`);
611
+ }
612
+ if (msgId && conversationId) try {
613
+ await recallEmotionReply(account.config, {
614
+ msgId,
615
+ conversationId,
616
+ robotCode: account.config.clientId
617
+ }, log);
618
+ results.emotion = { ok: true };
619
+ log?.info?.(`[Gateway][fixStuckCards] 表情撤回成功: msgId=${msgId}`);
620
+ } catch (err) {
621
+ results.emotion = {
622
+ ok: false,
623
+ error: err.message
624
+ };
625
+ log?.error?.(`[Gateway][fixStuckCards] 表情撤回失败: ${err.message}`);
626
+ }
627
+ else if (msgId && !conversationId) results.emotion = {
628
+ ok: false,
629
+ error: "conversationId is required together with msgId"
630
+ };
631
+ respond(Object.values(results).every((r) => r.ok), results);
632
+ } catch (err) {
633
+ log?.error?.(`[Gateway][fixStuckCards] 错误: ${err.message}`);
634
+ respond(false, { error: err.message });
635
+ }
636
+ });
637
+ /**
638
+ * 列出所有已配置的钉钉机器人账号(含元数据),供多 Agent 协作时查询"队友机器人"使用。
639
+ *
640
+ * 返回字段:
641
+ * - accountId: 在 openclaw.json 里 accounts 配置的 key(用于 sendToGroup 的 accountId 参数)
642
+ * - name: 友好显示名(accounts.<id>.name)
643
+ * - chatbotUserId: 该机器人加密 ID(如配置在 accounts.<id>.chatbotUserId 里),可用于 atDingtalkIds
644
+ * - clientId: AppKey(脱敏前 8 位)
645
+ *
646
+ * @example
647
+ * ```typescript
648
+ * const r = await gateway.call('dingtalk-connector.listAccounts');
649
+ * // -> [{ accountId: 'main-bot', name: '主助手机器人', chatbotUserId: '$:LWCP_v1:xxx', ... }, ...]
650
+ * ```
651
+ */
652
+ api.registerGatewayMethod("dingtalk-connector.listAccounts", async ({ context, respond }) => {
653
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
654
+ const cfg = loadConfig();
655
+ try {
656
+ const root = cfg.channels?.["dingtalk-connector"];
657
+ const accountsMap = root?.accounts || {};
658
+ const mentionTable = buildBotMentionTable(cfg);
659
+ const mentionByAccountId = new Map(mentionTable.map((e) => [e.accountId, e]));
660
+ respond(true, { accounts: Object.keys(accountsMap).map((id) => {
661
+ const a = accountsMap[id] || {};
662
+ const cid = String(a.clientId ?? root?.clientId ?? "");
663
+ const mention = mentionByAccountId.get(id);
664
+ return {
665
+ accountId: id,
666
+ name: a.name || id,
667
+ enabled: a.enabled !== false,
668
+ chatbotUserId: a.chatbotUserId || void 0,
669
+ chatbotCorpId: a.chatbotCorpId || void 0,
670
+ clientId: cid ? cid.substring(0, 8) + "..." : void 0,
671
+ agentIds: mention?.agentIds || [],
672
+ aliases: mention?.aliases || [],
673
+ mentionReady: !!a.chatbotUserId
674
+ };
675
+ }) });
676
+ } catch (err) {
677
+ log?.error?.(`[Gateway][listAccounts] 错误: ${err.message}`);
678
+ respond(false, { error: err.message });
679
+ }
680
+ });
681
+ /**
682
+ * 多 bot 协作自检:检查每个 account 是否具备在群里互 @ 的能力。
683
+ *
684
+ * 一个 bot 能被其它 bot @ 的前提:
685
+ * 1. `accounts.<id>.chatbotUserId` / `chatbotCorpId` 已填(从 `[BotIdentity]` 日志抓回来)
686
+ * 2. bot 当前 enabled 且配了 clientId / clientSecret
687
+ *
688
+ * 返回的报告可以直接贴给用户,告诉他下一步该干什么
689
+ * (比如"给 dev-bot 发一条消息后回填 chatbotUserId")。
690
+ *
691
+ * @example
692
+ * ```typescript
693
+ * const r = await gateway.call('dingtalk-connector.bootstrapBotIdentity');
694
+ * // -> {
695
+ * // ready: false,
696
+ * // totalAccounts: 2,
697
+ * // readyAccounts: 1,
698
+ * // missingChatbotUserId: ['dev-bot'],
699
+ * // report: '...'
700
+ * // }
701
+ * ```
702
+ */
703
+ api.registerGatewayMethod("dingtalk-connector.bootstrapBotIdentity", async ({ context, respond }) => {
704
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
705
+ const cfg = loadConfig();
706
+ try {
707
+ const accountsMap = (cfg.channels?.["dingtalk-connector"])?.accounts || {};
708
+ const ids = Object.keys(accountsMap);
709
+ const missingChatbotUserId = [];
710
+ const missingCredentials = [];
711
+ const disabled = [];
712
+ const ready = [];
713
+ for (const id of ids) {
714
+ const a = accountsMap[id] || {};
715
+ if (a.enabled === false) {
716
+ disabled.push(id);
717
+ continue;
718
+ }
719
+ const hasCreds = !!(a.clientId && a.clientSecret);
720
+ if (!hasCreds) missingCredentials.push(id);
721
+ if (!a.chatbotUserId) missingChatbotUserId.push(id);
722
+ else if (hasCreds) ready.push({
723
+ accountId: id,
724
+ name: a.name,
725
+ chatbotUserId: a.chatbotUserId
726
+ });
727
+ }
728
+ const reportLines = [];
729
+ reportLines.push(`[BotIdentity] 已配置 ${ids.length} 个账号,其中 ${ready.length} 个可参与多 bot 互相 @`);
730
+ if (ready.length > 0) reportLines.push("[OK] Ready: " + ready.map((r) => `${r.accountId}(${r.name || ""})`).join(", "));
731
+ if (missingChatbotUserId.length > 0) reportLines.push(`[WARN] 缺少 chatbotUserId: ${missingChatbotUserId.join(", ")} — 请让这些 bot 在钉钉里各收一条消息,然后在终端 grep "[BotIdentity]" 抓到加密 ID 后回填到 openclaw.json 对应 account`);
732
+ if (missingCredentials.length > 0) reportLines.push(`[ERR] 缺少 clientId/clientSecret: ${missingCredentials.join(", ")}`);
733
+ if (disabled.length > 0) reportLines.push(`[PAUSED] 已禁用: ${disabled.join(", ")}`);
734
+ respond(true, {
735
+ ready: missingChatbotUserId.length === 0 && missingCredentials.length === 0 && ready.length > 0,
736
+ totalAccounts: ids.length,
737
+ readyAccounts: ready.length,
738
+ readyList: ready,
739
+ missingChatbotUserId,
740
+ missingCredentials,
741
+ disabled,
742
+ report: reportLines.join("\n")
743
+ });
744
+ } catch (err) {
745
+ log?.error?.(`[Gateway][bootstrapBotIdentity] 错误: ${err.message}`);
746
+ respond(false, { error: err.message });
747
+ }
748
+ });
749
+ api.registerGatewayMethod("dingtalk-connector.probe", async ({ context, respond }) => {
750
+ const { loadConfig } = await import("openclaw/plugin-sdk/config-runtime");
751
+ const cfg = loadConfig();
752
+ try {
753
+ const account = resolveDingtalkAccount({ cfg });
754
+ if (!account.config?.clientId || !account.config?.clientSecret) return respond(false, { error: "Not configured" });
755
+ const { getAccessToken } = await import("./utils-legacy-CFYDBM4r.mjs");
756
+ await getAccessToken(account.config);
757
+ respond(true, {
758
+ ok: true,
759
+ details: { clientId: account.config.clientId }
760
+ });
761
+ } catch (err) {
762
+ log?.error?.(`[Gateway][probe] 错误: ${err.message}`);
763
+ respond(false, {
764
+ ok: false,
765
+ error: err.message
766
+ });
767
+ }
768
+ });
769
+ }
770
+ //#endregion
771
+ export { registerGatewayMethods as t };