@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,114 @@
|
|
|
1
|
+
//#region src/utils/constants.ts
|
|
2
|
+
/**
|
|
3
|
+
* 常量定义模块
|
|
4
|
+
*/
|
|
5
|
+
/** 新会话触发命令 */
|
|
6
|
+
const NEW_SESSION_COMMANDS = [
|
|
7
|
+
"/new",
|
|
8
|
+
"/reset",
|
|
9
|
+
"/clear",
|
|
10
|
+
"新会话",
|
|
11
|
+
"重新开始",
|
|
12
|
+
"清空对话"
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* 媒体类消息类型集合。
|
|
16
|
+
*
|
|
17
|
+
* 这些消息类型需要通过钉钉原生消息 API 发送,不支持 AI Card 形式,
|
|
18
|
+
* 在 sendProactiveInternal 中会强制跳过 AI Card 路径。
|
|
19
|
+
*/
|
|
20
|
+
const MEDIA_MSG_TYPES = new Set([
|
|
21
|
+
"image",
|
|
22
|
+
"voice",
|
|
23
|
+
"file",
|
|
24
|
+
"video"
|
|
25
|
+
]);
|
|
26
|
+
/**
|
|
27
|
+
* 队列繁忙时的即时 ACK 回复短语。
|
|
28
|
+
*
|
|
29
|
+
* 当消息入队时检测到上一条消息仍在处理中,立即从此列表中随机选取一条回复,
|
|
30
|
+
* 告知用户消息已收到并排队,避免用户以为 Bot 没有响应。
|
|
31
|
+
* 参考 delivery.rs 中 DINGTALK_ACK_PHRASES_BUSY_ZH_CN 的设计。
|
|
32
|
+
*/
|
|
33
|
+
const QUEUE_BUSY_ACK_PHRASES = [
|
|
34
|
+
"上一条还没结束,这条我已经记下,稍后按顺序继续处理。",
|
|
35
|
+
"当前还在忙,你的新消息已经排队,上一条完成后我马上继续。",
|
|
36
|
+
"我这边还在处理上一条,这条已加入队列,完成后继续处理。"
|
|
37
|
+
];
|
|
38
|
+
//#endregion
|
|
39
|
+
//#region src/utils/session.ts
|
|
40
|
+
/**
|
|
41
|
+
* 会话管理模块
|
|
42
|
+
* 构建 OpenClaw 标准会话上下文
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* 构建 OpenClaw 标准会话上下文
|
|
46
|
+
* 遵循 OpenClaw session.dmScope 机制,让 Gateway 根据配置自动处理会话隔离
|
|
47
|
+
*
|
|
48
|
+
* @param sharedMemoryAcrossConversations - 是否在不同会话间共享记忆(默认 false)
|
|
49
|
+
* - true: 所有会话共享记忆,使用 accountId 作为记忆标识
|
|
50
|
+
* - false: 不同会话独立记忆,使用完整的 sessionContext 作为记忆标识
|
|
51
|
+
*/
|
|
52
|
+
function buildSessionContext(params) {
|
|
53
|
+
const { accountId, senderId, senderName, conversationType, conversationId, groupSubject, separateSessionByConversation, groupSessionScope, sharedMemoryAcrossConversations } = params;
|
|
54
|
+
const isDirect = conversationType === "1";
|
|
55
|
+
const peerId = isDirect ? senderId : conversationId || senderId;
|
|
56
|
+
if (sharedMemoryAcrossConversations === true) return {
|
|
57
|
+
channel: "dingtalk-connector",
|
|
58
|
+
accountId,
|
|
59
|
+
chatType: isDirect ? "direct" : "group",
|
|
60
|
+
peerId,
|
|
61
|
+
sessionPeerId: accountId,
|
|
62
|
+
conversationId: isDirect ? void 0 : conversationId,
|
|
63
|
+
senderName,
|
|
64
|
+
groupSubject: isDirect ? void 0 : groupSubject
|
|
65
|
+
};
|
|
66
|
+
if (separateSessionByConversation === false) return {
|
|
67
|
+
channel: "dingtalk-connector",
|
|
68
|
+
accountId,
|
|
69
|
+
chatType: isDirect ? "direct" : "group",
|
|
70
|
+
peerId,
|
|
71
|
+
sessionPeerId: senderId,
|
|
72
|
+
conversationId: isDirect ? void 0 : conversationId,
|
|
73
|
+
senderName,
|
|
74
|
+
groupSubject: isDirect ? void 0 : groupSubject
|
|
75
|
+
};
|
|
76
|
+
if (isDirect) return {
|
|
77
|
+
channel: "dingtalk-connector",
|
|
78
|
+
accountId,
|
|
79
|
+
chatType: "direct",
|
|
80
|
+
peerId,
|
|
81
|
+
sessionPeerId: senderId,
|
|
82
|
+
senderName
|
|
83
|
+
};
|
|
84
|
+
if (groupSessionScope === "group_sender") return {
|
|
85
|
+
channel: "dingtalk-connector",
|
|
86
|
+
accountId,
|
|
87
|
+
chatType: "group",
|
|
88
|
+
peerId,
|
|
89
|
+
sessionPeerId: `${conversationId}:${senderId}`,
|
|
90
|
+
conversationId,
|
|
91
|
+
senderName,
|
|
92
|
+
groupSubject
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
channel: "dingtalk-connector",
|
|
96
|
+
accountId,
|
|
97
|
+
chatType: "group",
|
|
98
|
+
peerId,
|
|
99
|
+
sessionPeerId: conversationId || senderId,
|
|
100
|
+
conversationId,
|
|
101
|
+
senderName,
|
|
102
|
+
groupSubject
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* 检查消息是否是新会话命令
|
|
107
|
+
*/
|
|
108
|
+
function normalizeSlashCommand(text) {
|
|
109
|
+
const lower = text.trim().toLowerCase();
|
|
110
|
+
if (NEW_SESSION_COMMANDS.some((cmd) => lower === cmd.toLowerCase())) return "/new";
|
|
111
|
+
return text;
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { QUEUE_BUSY_ACK_PHRASES as a, NEW_SESSION_COMMANDS as i, normalizeSlashCommand as n, MEDIA_MSG_TYPES as r, buildSessionContext as t };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { n as dingtalkOapiHttp, t as dingtalkHttp } from "./http-client-DFWZgO1n.mjs";
|
|
2
|
+
//#region src/utils/token.ts
|
|
3
|
+
const DINGTALK_API = "https://api.dingtalk.com";
|
|
4
|
+
const DINGTALK_OAPI = "https://oapi.dingtalk.com";
|
|
5
|
+
/**
|
|
6
|
+
* 按 clientId 分桶缓存,避免多账号串 token。
|
|
7
|
+
*/
|
|
8
|
+
const apiTokenCache = /* @__PURE__ */ new Map();
|
|
9
|
+
const oapiTokenCache = /* @__PURE__ */ new Map();
|
|
10
|
+
function cacheKey(config) {
|
|
11
|
+
const clientId = String(config?.clientId ?? "").trim();
|
|
12
|
+
if (!clientId) throw new Error("Invalid DingtalkConfig: clientId is required for token caching. Please ensure your configuration includes a valid clientId.");
|
|
13
|
+
return clientId;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 获取钉钉 Access Token(新版 API)
|
|
17
|
+
*/
|
|
18
|
+
async function getAccessToken(config) {
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const key = cacheKey(config);
|
|
21
|
+
const cached = apiTokenCache.get(key);
|
|
22
|
+
if (cached && cached.expiryMs > now + 6e4) return cached.token;
|
|
23
|
+
const response = await dingtalkHttp.post(`${DINGTALK_API}/v1.0/oauth2/accessToken`, {
|
|
24
|
+
appKey: config.clientId,
|
|
25
|
+
appSecret: config.clientSecret
|
|
26
|
+
});
|
|
27
|
+
const token = response.data.accessToken;
|
|
28
|
+
const expireInSec = Number(response.data.expireIn ?? 0);
|
|
29
|
+
apiTokenCache.set(key, {
|
|
30
|
+
token,
|
|
31
|
+
expiryMs: now + expireInSec * 1e3
|
|
32
|
+
});
|
|
33
|
+
return token;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 获取钉钉 OAPI Access Token(旧版 API,用于媒体上传等)
|
|
37
|
+
*/
|
|
38
|
+
async function getOapiAccessToken(config) {
|
|
39
|
+
try {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
const key = cacheKey(config);
|
|
42
|
+
const cached = oapiTokenCache.get(key);
|
|
43
|
+
if (cached && cached.expiryMs > now + 6e4) return cached.token;
|
|
44
|
+
const resp = await dingtalkOapiHttp.get(`${DINGTALK_OAPI}/gettoken`, { params: {
|
|
45
|
+
appkey: config.clientId,
|
|
46
|
+
appsecret: config.clientSecret
|
|
47
|
+
} });
|
|
48
|
+
if (resp.data?.errcode === 0 && resp.data?.access_token) {
|
|
49
|
+
const token = String(resp.data.access_token);
|
|
50
|
+
const expiresInSec = Number(resp.data.expires_in ?? 7200);
|
|
51
|
+
oapiTokenCache.set(key, {
|
|
52
|
+
token,
|
|
53
|
+
expiryMs: now + expiresInSec * 1e3
|
|
54
|
+
});
|
|
55
|
+
return token;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { getOapiAccessToken as i, DINGTALK_OAPI as n, getAccessToken as r, DINGTALK_API as t };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
//#region src/utils/utils-legacy.ts
|
|
2
|
+
/** 钉钉 API 常量 */
|
|
3
|
+
const DINGTALK_API = "https://api.dingtalk.com";
|
|
4
|
+
const DINGTALK_OAPI = "https://oapi.dingtalk.com";
|
|
5
|
+
const apiTokenCache = /* @__PURE__ */ new Map();
|
|
6
|
+
const oapiTokenCache = /* @__PURE__ */ new Map();
|
|
7
|
+
function cacheKey(config) {
|
|
8
|
+
const clientId = String(config?.clientId ?? "").trim();
|
|
9
|
+
if (!clientId) throw new Error("Invalid DingtalkConfig: clientId is required for token caching. Please ensure your configuration includes a valid clientId.");
|
|
10
|
+
return clientId;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 获取钉钉 Access Token(新版 API)
|
|
14
|
+
*/
|
|
15
|
+
async function getAccessToken(config) {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
const key = cacheKey(config);
|
|
18
|
+
const cached = apiTokenCache.get(key);
|
|
19
|
+
if (cached && cached.expiryMs > now + 6e4) return cached.token;
|
|
20
|
+
const { dingtalkHttp } = await import("./http-client-CpnJHB89.mjs");
|
|
21
|
+
const response = await dingtalkHttp.post(`${DINGTALK_API}/v1.0/oauth2/accessToken`, {
|
|
22
|
+
appKey: config.clientId,
|
|
23
|
+
appSecret: config.clientSecret
|
|
24
|
+
});
|
|
25
|
+
const token = response.data.accessToken;
|
|
26
|
+
const expireInSec = Number(response.data.expireIn ?? 0);
|
|
27
|
+
apiTokenCache.set(key, {
|
|
28
|
+
token,
|
|
29
|
+
expiryMs: now + expireInSec * 1e3
|
|
30
|
+
});
|
|
31
|
+
return token;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 获取钉钉 OAPI Access Token(旧版 API,用于媒体上传等)
|
|
35
|
+
*/
|
|
36
|
+
async function getOapiAccessToken(config) {
|
|
37
|
+
try {
|
|
38
|
+
const now = Date.now();
|
|
39
|
+
const key = cacheKey(config);
|
|
40
|
+
const cached = oapiTokenCache.get(key);
|
|
41
|
+
if (cached && cached.expiryMs > now + 6e4) return cached.token;
|
|
42
|
+
const { dingtalkOapiHttp } = await import("./http-client-CpnJHB89.mjs");
|
|
43
|
+
const resp = await dingtalkOapiHttp.get(`${DINGTALK_OAPI}/gettoken`, { params: {
|
|
44
|
+
appkey: config.clientId,
|
|
45
|
+
appsecret: config.clientSecret
|
|
46
|
+
} });
|
|
47
|
+
if (resp.data?.errcode === 0 && resp.data?.access_token) {
|
|
48
|
+
const token = String(resp.data.access_token);
|
|
49
|
+
const expiresInSec = Number(resp.data.expires_in ?? 7200);
|
|
50
|
+
oapiTokenCache.set(key, {
|
|
51
|
+
token,
|
|
52
|
+
expiryMs: now + expiresInSec * 1e3
|
|
53
|
+
});
|
|
54
|
+
return token;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** staffId → unionId 缓存(带过期时间的 LRU 缓存) */
|
|
62
|
+
const MAX_UNION_ID_CACHE_SIZE = 1e3;
|
|
63
|
+
const UNION_ID_CACHE_TTL = 1440 * 60 * 1e3;
|
|
64
|
+
const unionIdCache = /* @__PURE__ */ new Map();
|
|
65
|
+
/**
|
|
66
|
+
* 通过 oapi 旧版接口将 staffId 转换为 unionId
|
|
67
|
+
*/
|
|
68
|
+
async function getUnionId(staffId, config, log) {
|
|
69
|
+
const cached = unionIdCache.get(staffId);
|
|
70
|
+
if (cached && Date.now() - cached.timestamp < UNION_ID_CACHE_TTL) return cached.unionId;
|
|
71
|
+
try {
|
|
72
|
+
const token = await getOapiAccessToken(config);
|
|
73
|
+
if (!token) {
|
|
74
|
+
log?.error?.("[DingTalk] getUnionId: 无法获取 oapi access_token");
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const { dingtalkOapiHttp } = await import("./http-client-CpnJHB89.mjs");
|
|
78
|
+
const resp = await dingtalkOapiHttp.get(`${DINGTALK_OAPI}/user/get`, {
|
|
79
|
+
params: {
|
|
80
|
+
access_token: token,
|
|
81
|
+
userid: staffId
|
|
82
|
+
},
|
|
83
|
+
timeout: 1e4
|
|
84
|
+
});
|
|
85
|
+
const unionId = resp.data?.unionid;
|
|
86
|
+
if (unionId) {
|
|
87
|
+
if (unionIdCache.size >= MAX_UNION_ID_CACHE_SIZE) {
|
|
88
|
+
let oldestKey = null;
|
|
89
|
+
let oldestTime = Date.now();
|
|
90
|
+
for (const [key, entry] of unionIdCache.entries()) if (entry.timestamp < oldestTime) {
|
|
91
|
+
oldestTime = entry.timestamp;
|
|
92
|
+
oldestKey = key;
|
|
93
|
+
}
|
|
94
|
+
if (oldestKey) unionIdCache.delete(oldestKey);
|
|
95
|
+
}
|
|
96
|
+
unionIdCache.set(staffId, {
|
|
97
|
+
unionId,
|
|
98
|
+
timestamp: Date.now()
|
|
99
|
+
});
|
|
100
|
+
log?.info?.(`[DingTalk] getUnionId: ${staffId} → ${unionId}`);
|
|
101
|
+
return unionId;
|
|
102
|
+
}
|
|
103
|
+
log?.error?.(`[DingTalk] getUnionId: 响应中无 unionid 字段: ${JSON.stringify(resp.data)}`);
|
|
104
|
+
return null;
|
|
105
|
+
} catch (err) {
|
|
106
|
+
log?.error?.(`[DingTalk] getUnionId 失败: ${err.message}`);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/** 消息去重缓存 Map<messageId, timestamp> - 防止同一消息被重复处理 */
|
|
111
|
+
const processedMessages = /* @__PURE__ */ new Map();
|
|
112
|
+
/** 消息去重缓存过期时间(5分钟) */
|
|
113
|
+
const MESSAGE_DEDUP_TTL = 300 * 1e3;
|
|
114
|
+
/**
|
|
115
|
+
* 清理过期的消息去重缓存
|
|
116
|
+
*/
|
|
117
|
+
function cleanupProcessedMessages() {
|
|
118
|
+
const now = Date.now();
|
|
119
|
+
for (const [msgId, timestamp] of processedMessages.entries()) if (now - timestamp > MESSAGE_DEDUP_TTL) processedMessages.delete(msgId);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 检查消息是否已处理过(去重)
|
|
123
|
+
*/
|
|
124
|
+
function isMessageProcessed(messageId) {
|
|
125
|
+
if (!messageId) return false;
|
|
126
|
+
return processedMessages.has(messageId);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 标记消息为已处理
|
|
130
|
+
*/
|
|
131
|
+
function markMessageProcessed(messageId) {
|
|
132
|
+
if (!messageId) return;
|
|
133
|
+
processedMessages.set(messageId, Date.now());
|
|
134
|
+
if (processedMessages.size >= 100) cleanupProcessedMessages();
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 对钉钉 Stream 消息做双层去重检查,并在首次处理时标记。
|
|
138
|
+
*
|
|
139
|
+
* 背景:钉钉 Stream 模式存在两套消息 ID:
|
|
140
|
+
* - headers.messageId:WebSocket 协议层的投递 ID,每次重发都会生成新值
|
|
141
|
+
* - data.msgId:业务层的用户消息 ID,重发时保持不变
|
|
142
|
+
*
|
|
143
|
+
* 因此必须同时检查两个 ID,才能可靠地拦截钉钉服务端的重发消息:
|
|
144
|
+
* 1. 协议层去重(headers.messageId):拦截同一次投递的重复回调
|
|
145
|
+
* 2. 业务层去重(data.msgId):拦截 ~60 秒后服务端因未收到业务回复而触发的重发
|
|
146
|
+
*
|
|
147
|
+
* 重要:key 必须带 accountId 前缀,避免多账号(多机器人)场景下,
|
|
148
|
+
* 同一条群消息 @多个机器人时,不同机器人收到相同 msgId 导致误判为重复消息。
|
|
149
|
+
*
|
|
150
|
+
* @param accountId - 当前账号 ID(用于命名空间隔离,防止多账号误判)
|
|
151
|
+
* @param protocolMessageId - res.headers.messageId(WebSocket 协议层投递 ID)
|
|
152
|
+
* @param businessMsgId - data.msgId(钉钉业务层消息 ID,来自 JSON.parse(res.data).msgId)
|
|
153
|
+
* @returns true 表示消息已处理过(应跳过),false 表示首次处理(已标记为已处理)
|
|
154
|
+
*/
|
|
155
|
+
function checkAndMarkDingtalkMessage(accountId, protocolMessageId, businessMsgId) {
|
|
156
|
+
const scopedProtocolId = protocolMessageId ? `${accountId}:${protocolMessageId}` : void 0;
|
|
157
|
+
const scopedBusinessId = businessMsgId ? `${accountId}:${businessMsgId}` : void 0;
|
|
158
|
+
const isProtocolDuplicate = scopedProtocolId ? isMessageProcessed(scopedProtocolId) : false;
|
|
159
|
+
const isBusinessDuplicate = scopedBusinessId ? isMessageProcessed(scopedBusinessId) : false;
|
|
160
|
+
if (isProtocolDuplicate || isBusinessDuplicate) return true;
|
|
161
|
+
if (scopedProtocolId) markMessageProcessed(scopedProtocolId);
|
|
162
|
+
if (scopedBusinessId) markMessageProcessed(scopedBusinessId);
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 在用户消息上贴 🤔思考中 表情,表示正在处理
|
|
167
|
+
*/
|
|
168
|
+
async function addEmotionReply(config, data, log) {
|
|
169
|
+
if (!data.msgId || !data.conversationId) return;
|
|
170
|
+
try {
|
|
171
|
+
const token = await getAccessToken(config);
|
|
172
|
+
const { dingtalkHttp } = await import("./http-client-CpnJHB89.mjs");
|
|
173
|
+
await dingtalkHttp.post(`${DINGTALK_API}/v1.0/robot/emotion/reply`, {
|
|
174
|
+
robotCode: data.robotCode ?? config.clientId,
|
|
175
|
+
openMsgId: data.msgId,
|
|
176
|
+
openConversationId: data.conversationId,
|
|
177
|
+
emotionType: 2,
|
|
178
|
+
emotionName: "🤔思考中",
|
|
179
|
+
textEmotion: {
|
|
180
|
+
emotionId: "2659900",
|
|
181
|
+
emotionName: "🤔思考中",
|
|
182
|
+
text: "🤔思考中",
|
|
183
|
+
backgroundId: "im_bg_1"
|
|
184
|
+
}
|
|
185
|
+
}, {
|
|
186
|
+
headers: {
|
|
187
|
+
"x-acs-dingtalk-access-token": token,
|
|
188
|
+
"Content-Type": "application/json"
|
|
189
|
+
},
|
|
190
|
+
timeout: 5e3
|
|
191
|
+
});
|
|
192
|
+
log?.info?.(`[DingTalk][Emotion] 贴表情成功: msgId=${data.msgId}`);
|
|
193
|
+
} catch (err) {
|
|
194
|
+
log?.warn?.(`[DingTalk][Emotion] 贴表情失败(不影响主流程): ${err.message}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* 撤回用户消息上的 🤔思考中 表情
|
|
199
|
+
*/
|
|
200
|
+
async function recallEmotionReply(config, data, log) {
|
|
201
|
+
if (!data.msgId || !data.conversationId) return;
|
|
202
|
+
try {
|
|
203
|
+
const token = await getAccessToken(config);
|
|
204
|
+
const { dingtalkHttp } = await import("./http-client-CpnJHB89.mjs");
|
|
205
|
+
await dingtalkHttp.post(`${DINGTALK_API}/v1.0/robot/emotion/recall`, {
|
|
206
|
+
robotCode: data.robotCode ?? config.clientId,
|
|
207
|
+
openMsgId: data.msgId,
|
|
208
|
+
openConversationId: data.conversationId,
|
|
209
|
+
emotionType: 2,
|
|
210
|
+
emotionName: "🤔思考中",
|
|
211
|
+
textEmotion: {
|
|
212
|
+
emotionId: "2659900",
|
|
213
|
+
emotionName: "🤔思考中",
|
|
214
|
+
text: "🤔思考中",
|
|
215
|
+
backgroundId: "im_bg_1"
|
|
216
|
+
}
|
|
217
|
+
}, {
|
|
218
|
+
headers: {
|
|
219
|
+
"x-acs-dingtalk-access-token": token,
|
|
220
|
+
"Content-Type": "application/json"
|
|
221
|
+
},
|
|
222
|
+
timeout: 5e3
|
|
223
|
+
});
|
|
224
|
+
log?.info?.(`[DingTalk][Emotion] 撤回表情成功: msgId=${data.msgId}`);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
log?.warn?.(`[DingTalk][Emotion] 撤回表情失败(不影响主流程): ${err.message}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
//#endregion
|
|
230
|
+
export { cleanupProcessedMessages as a, getUnionId as c, recallEmotionReply as d, checkAndMarkDingtalkMessage as i, isMessageProcessed as l, DINGTALK_OAPI as n, getAccessToken as o, addEmotionReply as r, getOapiAccessToken as s, DINGTALK_API as t, markMessageProcessed as u };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# DingTalk DEAP Agent Integration
|
|
2
|
+
|
|
3
|
+
> [中文版](DEAP_AGENT_GUIDE.md)
|
|
4
|
+
|
|
5
|
+
Connect DingTalk [DEAP](https://deap.dingtalk.com) Agent with [OpenClaw](https://openclaw.ai) Gateway to enable natural language-driven local device operations.
|
|
6
|
+
|
|
7
|
+
## Key Features
|
|
8
|
+
|
|
9
|
+
- ✅ **Natural Language Interaction** - Users type natural language commands in the DingTalk chat (e.g., "Find PDF files on my desktop"), and the Agent automatically parses and executes the corresponding operations
|
|
10
|
+
- ✅ **NAT Traversal** - Designed for local devices without public IPs, establishing a stable communication tunnel between local and cloud environments via the Connector client
|
|
11
|
+
- ✅ **Cross-Platform Support** - Provides native binaries for Windows, macOS, and Linux, ensuring smooth operation across all platforms
|
|
12
|
+
|
|
13
|
+
## System Architecture
|
|
14
|
+
|
|
15
|
+
This solution uses a layered architecture with three core components:
|
|
16
|
+
|
|
17
|
+
1. **OpenClaw Gateway** - Deployed on the local device, provides a standardized HTTP interface for receiving and processing operation commands from the cloud, leveraging the OpenClaw engine to execute tasks
|
|
18
|
+
2. **DingTalk OpenClaw Connector** - Runs locally, building a communication tunnel between local and cloud environments to solve the problem of local devices without public IPs
|
|
19
|
+
3. **DingTalk DEAP MCP** - An extension module for the DEAP Agent, responsible for forwarding user natural language requests to the OpenClaw Gateway via the cloud tunnel
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
graph LR
|
|
23
|
+
subgraph "DingTalk App"
|
|
24
|
+
A["User chats with Agent"] --> B["DEAP Agent"]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
subgraph "Local Environment"
|
|
28
|
+
D["DingTalk OpenClaw Connector"] --> C["OpenClaw Gateway"]
|
|
29
|
+
C --> E["PC Operation Execution"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
B -.-> D
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Implementation Guide
|
|
36
|
+
|
|
37
|
+
### Step 1: Set Up the Local Environment
|
|
38
|
+
|
|
39
|
+
Ensure the OpenClaw Gateway is installed and running on your local device. The default address is `127.0.0.1:18789`:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
openclaw gateway start
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### Configure Gateway Parameters
|
|
46
|
+
|
|
47
|
+
1. Visit the [Configuration Page](http://127.0.0.1:18789/config)
|
|
48
|
+
2. In the **Overview**, set the Gateway Token and save it securely:
|
|
49
|
+

|
|
50
|
+
3. Switch to **Infrastructure** and enable the `OpenAI Chat Completions Endpoint`:
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
4. Click the `Save` button in the top-right corner to save your configuration
|
|
54
|
+
|
|
55
|
+
### Step 2: Obtain Required Parameters
|
|
56
|
+
|
|
57
|
+
#### Get corpId
|
|
58
|
+
|
|
59
|
+
Log in to the [DingTalk Developer Platform](https://open-dev.dingtalk.com) to find your enterprise CorpId:
|
|
60
|
+
|
|
61
|
+
<img width="864" height="450" alt="Get corpId from DingTalk Developer Platform" src="https://github.com/user-attachments/assets/18ec9830-2d43-489a-a73f-530972685225" />
|
|
62
|
+
|
|
63
|
+
#### Get apiKey
|
|
64
|
+
|
|
65
|
+
Log in to the [DingTalk DEAP Platform](https://deap.dingtalk.com), navigate to **Security & Permissions** → **API-Key Management** to create a new API Key:
|
|
66
|
+
|
|
67
|
+
<img width="1222" height="545" alt="DingTalk DEAP Platform API-Key Management" src="https://github.com/user-attachments/assets/dfe29984-4432-49c1-8226-0f9b60fbb5bc" />
|
|
68
|
+
|
|
69
|
+
### Step 3: Start the Connector Client
|
|
70
|
+
|
|
71
|
+
1. Download the installer for your operating system from the [Releases](https://github.com/hoskii/dingtalk-openclaw-connector/releases/tag/v0.0.1) page
|
|
72
|
+
2. Extract and run the Connector in the corresponding directory (macOS example):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
unzip connector-mac.zip
|
|
76
|
+
./connector-darwin -deapCorpId YOUR_CORP_ID -deapApiKey YOUR_API_KEY
|
|
77
|
+
```
|
|
78
|
+

|
|
79
|
+
|
|
80
|
+
### Step 4: Configure the DEAP Agent
|
|
81
|
+
|
|
82
|
+
1. Log in to the [DingTalk DEAP Platform](https://deap.dingtalk.com) and create a new agent:
|
|
83
|
+
|
|
84
|
+
<img width="2444" height="1486" alt="Create New Agent" src="https://github.com/user-attachments/assets/0b7f0855-f991-4aeb-b6e6-7576346b4477" />
|
|
85
|
+
|
|
86
|
+
2. In the skill management page, search for and integrate the OpenClaw skill:
|
|
87
|
+
|
|
88
|
+
<img width="3430" height="1732" alt="Add OpenClaw Skill" src="https://github.com/user-attachments/assets/d44f0038-f863-4c1f-afa7-b774d875e4ba" />
|
|
89
|
+
|
|
90
|
+
3. Configure skill parameters:
|
|
91
|
+
|
|
92
|
+
| Parameter | Source | Description |
|
|
93
|
+
| ------------ | ---------- | -------------------------------------------------------------------------------------- |
|
|
94
|
+
| apikey | From Step 2 | DEAP Platform API Key |
|
|
95
|
+
| apihost | Default | Typically `127.0.0.1:18789`. On Windows, you may need to use `localhost:18789` instead |
|
|
96
|
+
| gatewayToken | From Step 1 | Gateway authentication token |
|
|
97
|
+
|
|
98
|
+
<img width="3426" height="1752" alt="Configure OpenClaw Skill Parameters" src="https://github.com/user-attachments/assets/bc725789-382f-41b5-bbdb-ba8f29923d5c" />
|
|
99
|
+
|
|
100
|
+
Note that OpenClaw is an MCP, so you also need to configure its trigger rules. The MCP will only be invoked when the rules are satisfied:
|
|
101
|
+
<img width="1088" height="526" alt="image" src="https://github.com/user-attachments/assets/8b0b6f6d-70ff-4edc-b674-7a24126aadfa" />
|
|
102
|
+
|
|
103
|
+
4. Publish the Agent:
|
|
104
|
+
|
|
105
|
+
<img width="3416" height="1762" alt="Publish Agent" src="https://github.com/user-attachments/assets/3f8c3fdb-5f2b-4a4b-8896-35202e713bf3" />
|
|
106
|
+
|
|
107
|
+
### Step 5: Start Using
|
|
108
|
+
|
|
109
|
+
1. Search for and find your Agent in the DingTalk App:
|
|
110
|
+
|
|
111
|
+
<img width="1260" height="436" alt="Search for Agent" src="https://github.com/user-attachments/assets/30feff80-1b28-4274-830b-7045aed14980" />
|
|
112
|
+
|
|
113
|
+
2. Start your natural language conversation:
|
|
114
|
+
|
|
115
|
+
<img width="1896" height="1240" alt="Chat with Agent" src="https://github.com/user-attachments/assets/2a80aab8-3fbf-4d18-beea-770577cb1a40" />
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# 钉钉 DEAP Agent 集成
|
|
2
|
+
|
|
3
|
+
> [English Version](DEAP_AGENT_GUIDE.en.md)
|
|
4
|
+
|
|
5
|
+
通过将钉钉 [DEAP](https://deap.dingtalk.com) Agent 与 [OpenClaw](https://openclaw.ai) Gateway 连接,实现自然语言驱动的本地设备操作能力。
|
|
6
|
+
|
|
7
|
+
## 核心功能
|
|
8
|
+
|
|
9
|
+
- ✅ **自然语言交互** - 用户在钉钉对话框中输入自然语言指令(如"帮我查找桌面上的 PDF 文件"),Agent 将自动解析并执行相应操作
|
|
10
|
+
- ✅ **内网穿透机制** - 专为本地设备无公网 IP 场景设计,通过 Connector 客户端建立稳定的内外网通信隧道
|
|
11
|
+
- ✅ **跨平台兼容** - 提供 Windows、macOS 和 Linux 系统的原生二进制执行文件,确保各平台下的顺畅运行
|
|
12
|
+
|
|
13
|
+
## 系统架构
|
|
14
|
+
|
|
15
|
+
该方案采用分层架构模式,包含三个核心组件:
|
|
16
|
+
|
|
17
|
+
1. **OpenClaw Gateway** - 部署于本地设备,提供标准化 HTTP 接口,负责接收并处理来自云端的操作指令,调动 OpenClaw 引擎执行具体任务
|
|
18
|
+
2. **DingTalk OpenClaw Connector** - 运行于本地环境,构建本地与云端的通信隧道,解决内网设备无公网 IP 的问题
|
|
19
|
+
3. **DingTalk DEAP MCP** - 作为 DEAP Agent 的扩展能力模块,负责将用户自然语言请求经由云端隧道转发至 OpenClaw Gateway
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
graph LR
|
|
23
|
+
subgraph "钉钉 App"
|
|
24
|
+
A["用户与 Agent 对话"] --> B["DEAP Agent"]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
subgraph "本地环境"
|
|
28
|
+
D["DingTalk OpenClaw Connector"] --> C["OpenClaw Gateway"]
|
|
29
|
+
C --> E["PC 操作执行"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
B -.-> D
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 实施指南
|
|
36
|
+
|
|
37
|
+
### 第一步:部署本地环境
|
|
38
|
+
|
|
39
|
+
确认本地设备已成功安装并启动 OpenClaw Gateway,默认监听地址为 `127.0.0.1:18789`:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
openclaw gateway start
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
#### 配置 Gateway 参数
|
|
46
|
+
|
|
47
|
+
1. 访问 [配置页面](http://127.0.0.1:18789/config)
|
|
48
|
+
2. 在 **概览** 中设置 Gateway Token 并妥善保存:
|
|
49
|
+

|
|
50
|
+
3. 切换至 **基础设施**,启用 `OpenAI Chat Completions Endpoint` 功能:
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
4. 点击右上角 `Save` 按钮完成配置保存
|
|
54
|
+
|
|
55
|
+
### 第二步:获取必要参数
|
|
56
|
+
|
|
57
|
+
#### 获取 corpId
|
|
58
|
+
|
|
59
|
+
登录 [钉钉开发者平台](https://open-dev.dingtalk.com) 查看企业 CorpId:
|
|
60
|
+
|
|
61
|
+
<img width="864" height="450" alt="钉钉开发者平台获取 corpId" src="https://github.com/user-attachments/assets/18ec9830-2d43-489a-a73f-530972685225" />
|
|
62
|
+
|
|
63
|
+
#### 获取 apiKey
|
|
64
|
+
|
|
65
|
+
登录 [钉钉 DEAP 平台](https://deap.dingtalk.com),在 **安全与权限** → **API-Key 管理** 页面创建新的 API Key:
|
|
66
|
+
|
|
67
|
+
<img width="1222" height="545" alt="钉钉 DEAP 平台 API-Key 管理" src="https://github.com/user-attachments/assets/dfe29984-4432-49c1-8226-0f9b60fbb5bc" />
|
|
68
|
+
|
|
69
|
+
### 第三步:启动 Connector 客户端
|
|
70
|
+
|
|
71
|
+
1. 从 [Releases](https://github.com/hoskii/dingtalk-openclaw-connector/releases/tag/v0.0.1) 页面下载适配您操作系统的安装包
|
|
72
|
+
2. 解压后在对应目录运行 Connector(以 macOS 为例):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
unzip connector-mac.zip
|
|
76
|
+
./connector-darwin -deapCorpId YOUR_CORP_ID -deapApiKey YOUR_API_KEY
|
|
77
|
+
```
|
|
78
|
+

|
|
79
|
+
|
|
80
|
+
### 第四步:配置 DEAP Agent
|
|
81
|
+
|
|
82
|
+
1. 登录 [钉钉 DEAP 平台](https://deap.dingtalk.com),创建新的智能体:
|
|
83
|
+
|
|
84
|
+
<img width="2444" height="1486" alt="新建智能体界面" src="https://github.com/user-attachments/assets/0b7f0855-f991-4aeb-b6e6-7576346b4477" />
|
|
85
|
+
|
|
86
|
+
2. 在技能管理页面,搜索并集成 OpenClaw 技能:
|
|
87
|
+
|
|
88
|
+
<img width="3430" height="1732" alt="添加 OpenClaw 技能" src="https://github.com/user-attachments/assets/d44f0038-f863-4c1f-afa7-b774d875e4ba" />
|
|
89
|
+
|
|
90
|
+
3. 配置技能参数:
|
|
91
|
+
|
|
92
|
+
| 参数 | 来源 | 说明 |
|
|
93
|
+
| ------------ | ---------- | -------------------------------------------------------------------------------------- |
|
|
94
|
+
| apikey | 第二步获取 | DEAP 平台 API Key |
|
|
95
|
+
| apihost | 默认值 | 通常为 `127.0.0.1:18789`,在Windows环境下可能需要配置为 `localhost:18789` 才能正常工作 |
|
|
96
|
+
| gatewayToken | 第一步获取 | Gateway 配置的认证令牌 |
|
|
97
|
+
|
|
98
|
+
<img width="3426" height="1752" alt="配置 OpenClaw 技能参数" src="https://github.com/user-attachments/assets/bc725789-382f-41b5-bbdb-ba8f29923d5c" />
|
|
99
|
+
|
|
100
|
+
注意 OpenClaw 属于一个MCP,还需要配置他的触发规则,满足规则的情况下才会使用这个MCP:
|
|
101
|
+
<img width="1088" height="526" alt="image" src="https://github.com/user-attachments/assets/8b0b6f6d-70ff-4edc-b674-7a24126aadfa" />
|
|
102
|
+
|
|
103
|
+
4. 发布 Agent:
|
|
104
|
+
|
|
105
|
+
<img width="3416" height="1762" alt="发布 Agent" src="https://github.com/user-attachments/assets/3f8c3fdb-5f2b-4a4b-8896-35202e713bf3" />
|
|
106
|
+
|
|
107
|
+
### 第五步:开始使用
|
|
108
|
+
|
|
109
|
+
1. 在钉钉 App 中搜索并找到您创建的 Agent:
|
|
110
|
+
|
|
111
|
+
<img width="1260" height="436" alt="搜索 Agent" src="https://github.com/user-attachments/assets/30feff80-1b28-4274-830b-7045aed14980" />
|
|
112
|
+
|
|
113
|
+
2. 开始自然语言对话体验:
|
|
114
|
+
|
|
115
|
+
<img width="1896" height="1240" alt="与 Agent 对话" src="https://github.com/user-attachments/assets/2a80aab8-3fbf-4d18-beea-770577cb1a40" />
|