@junjiezhang/openclaw-wecom-plugin 1.0.0 → 1.0.2
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/dist/index.js +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +7 -1
- package/src/accounts.ts +0 -81
- package/src/bot.ts +0 -410
- package/src/channel.ts +0 -278
- package/src/client.ts +0 -55
- package/src/config-schema.ts +0 -102
- package/src/dedup.ts +0 -60
- package/src/directory.ts +0 -150
- package/src/index.ts +0 -20
- package/src/media.ts +0 -105
- package/src/monitor.ts +0 -344
- package/src/outbound.ts +0 -26
- package/src/policy.ts +0 -108
- package/src/probe.ts +0 -13
- package/src/reply-dispatcher.ts +0 -78
- package/src/runtime.ts +0 -14
- package/src/send.ts +0 -91
- package/src/targets.ts +0 -21
- package/src/types.d.ts +0 -17
- package/src/types.ts +0 -3
- package/tsconfig.json +0 -32
- package/types.d.ts +0 -43
package/dist/index.js
CHANGED
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@junjiezhang/openclaw-wecom-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "OpenClaw WeCom (企业微信) channel plugin - Send and receive messages via WeCom",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"openclaw.plugin.json",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
8
14
|
"keywords": [
|
|
9
15
|
"openclaw",
|
|
10
16
|
"plugin",
|
package/src/accounts.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import type { ClawdbotConfig } from "openclaw/plugin-sdk";
|
|
2
|
-
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk";
|
|
3
|
-
import type { WeComConfig, ResolvedWeComAccount } from "./types.js";
|
|
4
|
-
|
|
5
|
-
export function listWeComAccountIds(cfg: ClawdbotConfig): string[] {
|
|
6
|
-
const wecomCfg = cfg.channels?.wecom as WeComConfig | undefined;
|
|
7
|
-
if (!wecomCfg) return [];
|
|
8
|
-
return [DEFAULT_ACCOUNT_ID];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function resolveDefaultWeComAccountId(cfg: ClawdbotConfig): string {
|
|
12
|
-
return DEFAULT_ACCOUNT_ID;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function resolveWeComAccount({
|
|
16
|
-
cfg,
|
|
17
|
-
accountId,
|
|
18
|
-
}: {
|
|
19
|
-
cfg: ClawdbotConfig;
|
|
20
|
-
accountId?: string;
|
|
21
|
-
}): ResolvedWeComAccount {
|
|
22
|
-
const id = accountId ?? DEFAULT_ACCOUNT_ID;
|
|
23
|
-
const wecomCfg = cfg.channels?.wecom as WeComConfig | undefined;
|
|
24
|
-
|
|
25
|
-
if (!wecomCfg) {
|
|
26
|
-
return {
|
|
27
|
-
accountId: id,
|
|
28
|
-
enabled: false,
|
|
29
|
-
configured: false,
|
|
30
|
-
name: "default",
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const enabled = wecomCfg.enabled ?? false;
|
|
35
|
-
const configured = !!(
|
|
36
|
-
wecomCfg.corpId &&
|
|
37
|
-
wecomCfg.agentId &&
|
|
38
|
-
wecomCfg.secret &&
|
|
39
|
-
wecomCfg.token &&
|
|
40
|
-
wecomCfg.encodingAESKey
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
accountId: id,
|
|
45
|
-
enabled,
|
|
46
|
-
configured,
|
|
47
|
-
name: "default",
|
|
48
|
-
corpId: wecomCfg.corpId,
|
|
49
|
-
agentId: wecomCfg.agentId,
|
|
50
|
-
config: wecomCfg,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function resolveWeComCredentials({
|
|
55
|
-
cfg,
|
|
56
|
-
accountId,
|
|
57
|
-
}: {
|
|
58
|
-
cfg: ClawdbotConfig;
|
|
59
|
-
accountId?: string;
|
|
60
|
-
}): {
|
|
61
|
-
corpId: string;
|
|
62
|
-
agentId: string;
|
|
63
|
-
secret: string;
|
|
64
|
-
token?: string;
|
|
65
|
-
encodingAESKey?: string;
|
|
66
|
-
} {
|
|
67
|
-
const account = resolveWeComAccount({ cfg, accountId });
|
|
68
|
-
const wecomCfg = account.config;
|
|
69
|
-
|
|
70
|
-
if (!wecomCfg?.corpId || !wecomCfg?.agentId || !wecomCfg?.secret) {
|
|
71
|
-
throw new Error("WeCom credentials not configured");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
corpId: wecomCfg.corpId,
|
|
76
|
-
agentId: wecomCfg.agentId,
|
|
77
|
-
secret: wecomCfg.secret,
|
|
78
|
-
token: wecomCfg.token,
|
|
79
|
-
encodingAESKey: wecomCfg.encodingAESKey,
|
|
80
|
-
};
|
|
81
|
-
}
|
package/src/bot.ts
DELETED
|
@@ -1,410 +0,0 @@
|
|
|
1
|
-
import type { ClawdbotConfig, RuntimeEnv, HistoryEntry } from "openclaw/plugin-sdk";
|
|
2
|
-
import {
|
|
3
|
-
buildAgentMediaPayload,
|
|
4
|
-
buildPendingHistoryContextFromMap,
|
|
5
|
-
clearHistoryEntriesIfEnabled,
|
|
6
|
-
createScopedPairingAccess,
|
|
7
|
-
recordPendingHistoryEntryIfEnabled,
|
|
8
|
-
} from "openclaw/plugin-sdk";
|
|
9
|
-
import { resolveWeComAccount } from "./accounts.js";
|
|
10
|
-
import { downloadImageWeCom, saveInboundImage } from "./media.js";
|
|
11
|
-
import { createWeComReplyDispatcher } from "./reply-dispatcher.js";
|
|
12
|
-
import { getWeComRuntime } from "./runtime.js";
|
|
13
|
-
import { sendMessageWeCom, sendGroupMessageWeCom } from "./send.js";
|
|
14
|
-
|
|
15
|
-
export interface WeComMessageEvent {
|
|
16
|
-
ToUserName: string; // 企业微信 CorpID
|
|
17
|
-
FromUserName: string; // 发送者 UserID
|
|
18
|
-
CreateTime: number; // 消息创建时间
|
|
19
|
-
MsgType: string; // 消息类型:text, image, voice, video, file, location, link
|
|
20
|
-
Content?: string; // 文本消息内容
|
|
21
|
-
MsgId: string; // 消息 ID
|
|
22
|
-
AgentID: string; // 企业应用 ID
|
|
23
|
-
// Image message
|
|
24
|
-
PicUrl?: string; // 图片链接
|
|
25
|
-
MediaId?: string; // 媒体文件 ID
|
|
26
|
-
// File message
|
|
27
|
-
Title?: string; // 文件名
|
|
28
|
-
Description?: string; // 文件描述
|
|
29
|
-
FileKey?: string; // 文件 Key
|
|
30
|
-
// Location message
|
|
31
|
-
Location_X?: string; // 纬度
|
|
32
|
-
Location_Y?: string; // 经度
|
|
33
|
-
Scale?: string; // 地图缩放大小
|
|
34
|
-
Label?: string; // 地理位置信息
|
|
35
|
-
// Link message
|
|
36
|
-
Url?: string; // 链接地址
|
|
37
|
-
// Group chat
|
|
38
|
-
ChatId?: string; // 群聊 ID (when in group)
|
|
39
|
-
ChatType?: string; // 聊天类型: single (单聊) or group (群聊)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Handle incoming WeCom message
|
|
44
|
-
*/
|
|
45
|
-
export async function handleWeComMessage({
|
|
46
|
-
cfg,
|
|
47
|
-
event,
|
|
48
|
-
runtime,
|
|
49
|
-
chatHistories,
|
|
50
|
-
accountId,
|
|
51
|
-
}: {
|
|
52
|
-
cfg: ClawdbotConfig;
|
|
53
|
-
event: WeComMessageEvent;
|
|
54
|
-
runtime?: RuntimeEnv;
|
|
55
|
-
chatHistories: Map<string, HistoryEntry[]>;
|
|
56
|
-
accountId?: string;
|
|
57
|
-
}): Promise<void> {
|
|
58
|
-
const log = runtime?.log ?? console.log;
|
|
59
|
-
const error = runtime?.error ?? console.error;
|
|
60
|
-
|
|
61
|
-
// Media list for images/files
|
|
62
|
-
const mediaList: Array<{ path: string; contentType?: string | null }> = [];
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const userId = event.FromUserName;
|
|
66
|
-
const messageId = event.MsgId;
|
|
67
|
-
const msgType = event.MsgType;
|
|
68
|
-
const chatId = event.ChatId;
|
|
69
|
-
const chatType = event.ChatType ?? (chatId ? "group" : "single");
|
|
70
|
-
const isGroupChat = chatType === "group" || !!chatId;
|
|
71
|
-
|
|
72
|
-
// Resolve configured history limit (group vs DM)
|
|
73
|
-
const wecomCfg = resolveWeComAccount({ cfg, accountId }).config;
|
|
74
|
-
const DEFAULT_HISTORY_LIMIT = 20;
|
|
75
|
-
const historyLimit = isGroupChat
|
|
76
|
-
? (wecomCfg?.historyLimit ?? DEFAULT_HISTORY_LIMIT)
|
|
77
|
-
: (wecomCfg?.dmHistoryLimit ?? DEFAULT_HISTORY_LIMIT);
|
|
78
|
-
|
|
79
|
-
// Check for duplicate messages
|
|
80
|
-
try {
|
|
81
|
-
const { tryRecordMessagePersistent } = await import("./dedup.js");
|
|
82
|
-
const isNew = await tryRecordMessagePersistent(messageId, accountId ?? "default", log);
|
|
83
|
-
if (!isNew) {
|
|
84
|
-
log(`wecom[${accountId}]: duplicate message ${messageId}, skipping`);
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
} catch (dedupErr) {
|
|
88
|
-
error(`wecom[${accountId}]: dedup check failed: ${String(dedupErr)}`);
|
|
89
|
-
// Continue processing even if dedup fails
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
log(
|
|
93
|
-
`wecom[${accountId}]: received ${msgType} message from ${userId} in ${isGroupChat ? "group" : "DM"}`,
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
// Build session key
|
|
97
|
-
const sessionKey = isGroupChat
|
|
98
|
-
? `wecom:${accountId}:group:${chatId}`
|
|
99
|
-
: `wecom:${accountId}:user:${userId}`;
|
|
100
|
-
|
|
101
|
-
// Get or create history
|
|
102
|
-
let history = chatHistories.get(sessionKey);
|
|
103
|
-
if (!history) {
|
|
104
|
-
history = [];
|
|
105
|
-
chatHistories.set(sessionKey, history);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let messageText = "";
|
|
109
|
-
|
|
110
|
-
// Handle different message types
|
|
111
|
-
switch (msgType) {
|
|
112
|
-
case "text":
|
|
113
|
-
messageText = event.Content ?? "";
|
|
114
|
-
break;
|
|
115
|
-
|
|
116
|
-
case "image":
|
|
117
|
-
// Download and save image
|
|
118
|
-
if (event.MediaId) {
|
|
119
|
-
try {
|
|
120
|
-
log(`wecom[${accountId}]: downloading image (mediaId=${event.MediaId})...`);
|
|
121
|
-
const { buffer, contentType, fileName } = await downloadImageWeCom({
|
|
122
|
-
cfg,
|
|
123
|
-
mediaId: event.MediaId,
|
|
124
|
-
accountId,
|
|
125
|
-
});
|
|
126
|
-
const filePath = await saveInboundImage({
|
|
127
|
-
buffer,
|
|
128
|
-
fileName: fileName || "image.jpg",
|
|
129
|
-
accountId: accountId || "default",
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Add to media list
|
|
133
|
-
mediaList.push({ path: filePath, contentType: contentType || null });
|
|
134
|
-
messageText = "[图片]";
|
|
135
|
-
log(`wecom[${accountId}]: image downloaded: ${filePath}`);
|
|
136
|
-
} catch (imgErr) {
|
|
137
|
-
log(`wecom[${accountId}]: image download failed: ${String(imgErr)}`);
|
|
138
|
-
messageText = "[图片]";
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
messageText = "[图片]";
|
|
142
|
-
}
|
|
143
|
-
break;
|
|
144
|
-
|
|
145
|
-
case "file":
|
|
146
|
-
messageText = `[文件: ${event.Title ?? "未知"}]`;
|
|
147
|
-
break;
|
|
148
|
-
|
|
149
|
-
case "location":
|
|
150
|
-
messageText = `[位置: ${event.Label ?? ""}] (${event.Location_X}, ${event.Location_Y})`;
|
|
151
|
-
break;
|
|
152
|
-
|
|
153
|
-
case "link":
|
|
154
|
-
messageText = `[链接: ${event.Title ?? ""}] ${event.Url ?? ""}`;
|
|
155
|
-
break;
|
|
156
|
-
|
|
157
|
-
case "voice":
|
|
158
|
-
case "video":
|
|
159
|
-
messageText = `[${msgType === "voice" ? "语音" : "视频"}]`;
|
|
160
|
-
log(`wecom[${accountId}]: ${msgType} message not fully supported yet`);
|
|
161
|
-
break;
|
|
162
|
-
|
|
163
|
-
default:
|
|
164
|
-
log(`wecom[${accountId}]: unsupported message type: ${msgType}`);
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// For group chats, check if bot should respond
|
|
169
|
-
// Check group policy and allowlist
|
|
170
|
-
if (isGroupChat) {
|
|
171
|
-
const account = resolveWeComAccount({ cfg, accountId });
|
|
172
|
-
const wecomCfg = account.config;
|
|
173
|
-
|
|
174
|
-
// Import policy functions
|
|
175
|
-
const { resolveWeComReplyPolicy, isWeComGroupAllowed } = await import("./policy.js");
|
|
176
|
-
const { resolveDefaultGroupPolicy, resolveAllowlistProviderRuntimeGroupPolicy } =
|
|
177
|
-
await import("openclaw/plugin-sdk");
|
|
178
|
-
|
|
179
|
-
const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg);
|
|
180
|
-
const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({
|
|
181
|
-
providerConfigPresent: cfg.channels?.wecom !== undefined,
|
|
182
|
-
groupPolicy: wecomCfg?.groupPolicy,
|
|
183
|
-
defaultGroupPolicy,
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
// Check if group is allowed — use chatId, not userId (groupAllowFrom contains group IDs)
|
|
187
|
-
if (
|
|
188
|
-
!isWeComGroupAllowed({
|
|
189
|
-
groupPolicy,
|
|
190
|
-
allowFrom: wecomCfg?.groupAllowFrom ?? [],
|
|
191
|
-
senderId: chatId ?? "",
|
|
192
|
-
})
|
|
193
|
-
) {
|
|
194
|
-
log(`wecom[${accountId}]: group ${chatId} not in allowlist, skipping`);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// Check mention policy
|
|
199
|
-
const { requireMention } = resolveWeComReplyPolicy({
|
|
200
|
-
isDirectMessage: false,
|
|
201
|
-
globalConfig: wecomCfg,
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
if (requireMention) {
|
|
205
|
-
// TODO: Implement proper @mention detection for WeCom
|
|
206
|
-
// For now, skip if requireMention is true (WeCom doesn't provide mention info in basic events)
|
|
207
|
-
log(`wecom[${accountId}]: group message without mention, skipping (requireMention=true)`);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// For DMs, enforce dmPolicy (allowlist / pairing)
|
|
213
|
-
if (!isGroupChat) {
|
|
214
|
-
const account = resolveWeComAccount({ cfg, accountId });
|
|
215
|
-
const wecomCfg = account.config;
|
|
216
|
-
const dmPolicy = wecomCfg?.dmPolicy ?? "pairing";
|
|
217
|
-
|
|
218
|
-
if (dmPolicy !== "open") {
|
|
219
|
-
const { resolveWeComAllowlistMatch } = await import("./policy.js");
|
|
220
|
-
const core = getWeComRuntime();
|
|
221
|
-
const pairing = createScopedPairingAccess({
|
|
222
|
-
core,
|
|
223
|
-
channel: "wecom",
|
|
224
|
-
accountId: account.accountId,
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
const configAllowFrom = (wecomCfg?.allowFrom ?? []).map(String);
|
|
228
|
-
const storeAllowFrom =
|
|
229
|
-
dmPolicy === "pairing" ? await pairing.readAllowFromStore().catch(() => []) : [];
|
|
230
|
-
const effectiveDmAllowFrom = [...configAllowFrom, ...storeAllowFrom];
|
|
231
|
-
|
|
232
|
-
const dmAllowed = resolveWeComAllowlistMatch({
|
|
233
|
-
allowFrom: effectiveDmAllowFrom,
|
|
234
|
-
senderId: userId,
|
|
235
|
-
}).allowed;
|
|
236
|
-
|
|
237
|
-
if (!dmAllowed) {
|
|
238
|
-
if (dmPolicy === "pairing") {
|
|
239
|
-
const { code, created } = await pairing.upsertPairingRequest({
|
|
240
|
-
id: userId,
|
|
241
|
-
meta: { name: userId },
|
|
242
|
-
});
|
|
243
|
-
if (created) {
|
|
244
|
-
log(`wecom[${accountId}]: pairing request from ${userId}`);
|
|
245
|
-
try {
|
|
246
|
-
await sendMessageWeCom({
|
|
247
|
-
cfg,
|
|
248
|
-
to: userId,
|
|
249
|
-
text: core.channel.pairing.buildPairingReply({
|
|
250
|
-
channel: "wecom",
|
|
251
|
-
idLine: `Your WeCom user ID: ${userId}`,
|
|
252
|
-
code,
|
|
253
|
-
}),
|
|
254
|
-
accountId,
|
|
255
|
-
});
|
|
256
|
-
} catch (pairErr) {
|
|
257
|
-
log(`wecom[${accountId}]: pairing reply failed for ${userId}: ${String(pairErr)}`);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
} else {
|
|
261
|
-
log(
|
|
262
|
-
`wecom[${accountId}]: blocked unauthorized DM from ${userId} (dmPolicy=${dmPolicy})`,
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Get sender name for group chats
|
|
271
|
-
let senderName = userId;
|
|
272
|
-
if (isGroupChat) {
|
|
273
|
-
try {
|
|
274
|
-
const { getUserInfoWeCom } = await import("./directory.js");
|
|
275
|
-
const userInfo = await getUserInfoWeCom({ cfg, userId, accountId });
|
|
276
|
-
senderName = userInfo.name || userId;
|
|
277
|
-
} catch (err) {
|
|
278
|
-
log(`wecom[${accountId}]: failed to get sender name: ${String(err)}`);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Prefix message with sender name in group chats
|
|
283
|
-
const displayMessage = isGroupChat ? `${senderName}: ${messageText}` : messageText;
|
|
284
|
-
|
|
285
|
-
// Record user message in history
|
|
286
|
-
recordPendingHistoryEntryIfEnabled({
|
|
287
|
-
historyMap: chatHistories,
|
|
288
|
-
historyKey: sessionKey,
|
|
289
|
-
entry: {
|
|
290
|
-
sender: senderName,
|
|
291
|
-
body: messageText,
|
|
292
|
-
timestamp: event.CreateTime * 1000,
|
|
293
|
-
messageId: messageId,
|
|
294
|
-
},
|
|
295
|
-
limit: historyLimit,
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
// Build context for agent
|
|
299
|
-
const core = getWeComRuntime();
|
|
300
|
-
const historyContext = buildPendingHistoryContextFromMap({
|
|
301
|
-
historyMap: chatHistories,
|
|
302
|
-
historyKey: sessionKey,
|
|
303
|
-
limit: historyLimit,
|
|
304
|
-
currentMessage: displayMessage,
|
|
305
|
-
formatEntry: (entry) =>
|
|
306
|
-
core.channel.reply.formatAgentEnvelope({
|
|
307
|
-
channel: "WeCom",
|
|
308
|
-
from: isGroupChat ? `group:${chatId}` : userId,
|
|
309
|
-
timestamp: new Date(),
|
|
310
|
-
envelope: core.channel.reply.resolveEnvelopeFormatOptions(cfg),
|
|
311
|
-
body: `${entry.sender}: ${entry.body}`,
|
|
312
|
-
}),
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
// Resolve agent route
|
|
316
|
-
const account = resolveWeComAccount({ cfg, accountId });
|
|
317
|
-
const route = core.channel.routing.resolveAgentRoute({
|
|
318
|
-
cfg,
|
|
319
|
-
channel: "wecom",
|
|
320
|
-
accountId: account.accountId,
|
|
321
|
-
peer: { kind: isGroupChat ? "group" : "direct", id: isGroupChat ? (chatId ?? "") : userId },
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
// Create reply dispatcher
|
|
325
|
-
const { dispatcher, replyOptions, markDispatchIdle } = createWeComReplyDispatcher({
|
|
326
|
-
cfg,
|
|
327
|
-
agentId: route.agentId,
|
|
328
|
-
runtime: runtime as RuntimeEnv,
|
|
329
|
-
userId,
|
|
330
|
-
chatId,
|
|
331
|
-
isGroupChat,
|
|
332
|
-
accountId: account.accountId,
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
log(`wecom[${accountId}]: dispatching to agent (session=${route.sessionKey})`);
|
|
336
|
-
|
|
337
|
-
// Build media payload
|
|
338
|
-
const mediaPayload = buildAgentMediaPayload(mediaList);
|
|
339
|
-
|
|
340
|
-
// Build context payload
|
|
341
|
-
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
342
|
-
CommandBody: historyContext,
|
|
343
|
-
From: userId,
|
|
344
|
-
To: account.corpId ?? "",
|
|
345
|
-
SessionKey: route.sessionKey,
|
|
346
|
-
AccountId: route.accountId,
|
|
347
|
-
ChatType: isGroupChat ? "group" : "direct",
|
|
348
|
-
GroupSubject: isGroupChat ? chatId : undefined,
|
|
349
|
-
SenderName: senderName,
|
|
350
|
-
SenderId: userId,
|
|
351
|
-
Provider: "wecom" as const,
|
|
352
|
-
Surface: "wecom" as const,
|
|
353
|
-
MessageSid: messageId,
|
|
354
|
-
Timestamp: Date.now(),
|
|
355
|
-
CommandAuthorized: true,
|
|
356
|
-
OriginatingChannel: "wecom" as const,
|
|
357
|
-
OriginatingTo: account.corpId ?? "",
|
|
358
|
-
...mediaPayload,
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
// Dispatch to agent
|
|
362
|
-
const { queuedFinal, counts } = await core.channel.reply.dispatchReplyFromConfig({
|
|
363
|
-
ctx: ctxPayload,
|
|
364
|
-
cfg,
|
|
365
|
-
dispatcher,
|
|
366
|
-
replyOptions,
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
markDispatchIdle();
|
|
370
|
-
|
|
371
|
-
clearHistoryEntriesIfEnabled({
|
|
372
|
-
historyMap: chatHistories,
|
|
373
|
-
historyKey: sessionKey,
|
|
374
|
-
limit: historyLimit,
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
log(
|
|
378
|
-
`wecom[${accountId}]: dispatch complete (queuedFinal=${queuedFinal}, replies=${counts.final})`,
|
|
379
|
-
);
|
|
380
|
-
} catch (err) {
|
|
381
|
-
error(`wecom[${accountId}]: error handling message: ${String(err)}`);
|
|
382
|
-
if (err instanceof Error && err.stack) {
|
|
383
|
-
error(`wecom[${accountId}]: stack trace: ${err.stack}`);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Try to send error message
|
|
387
|
-
try {
|
|
388
|
-
const chatId = event.ChatId;
|
|
389
|
-
const isGroupChat = event.ChatType === "group" || !!chatId;
|
|
390
|
-
|
|
391
|
-
if (isGroupChat && chatId) {
|
|
392
|
-
await sendGroupMessageWeCom({
|
|
393
|
-
cfg,
|
|
394
|
-
chatId,
|
|
395
|
-
text: "抱歉,处理消息时出现错误。",
|
|
396
|
-
accountId,
|
|
397
|
-
});
|
|
398
|
-
} else {
|
|
399
|
-
await sendMessageWeCom({
|
|
400
|
-
cfg,
|
|
401
|
-
to: event.FromUserName,
|
|
402
|
-
text: "抱歉,处理消息时出现错误。",
|
|
403
|
-
accountId,
|
|
404
|
-
});
|
|
405
|
-
}
|
|
406
|
-
} catch (sendErr) {
|
|
407
|
-
error(`wecom[${accountId}]: failed to send error message: ${String(sendErr)}`);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|