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