@yanhaidao/wecom 2.2.7 → 2.3.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.
Files changed (54) hide show
  1. package/.github/workflows/release.yml +56 -0
  2. package/CLAUDE.md +1 -1
  3. package/GOVERNANCE.md +26 -0
  4. package/LICENSE +7 -0
  5. package/README.md +275 -91
  6. package/assets/01.bot-add.png +0 -0
  7. package/assets/01.bot-setp2.png +0 -0
  8. package/assets/02.agent.add.png +0 -0
  9. package/assets/02.agent.api-set.png +0 -0
  10. package/assets/register.png +0 -0
  11. package/changelog/v2.2.28.md +70 -0
  12. package/changelog/v2.3.2.md +70 -0
  13. package/compat-single-account.md +118 -0
  14. package/package.json +10 -2
  15. package/src/accounts.ts +17 -55
  16. package/src/agent/api-client.ts +84 -37
  17. package/src/agent/api-client.upload.test.ts +110 -0
  18. package/src/agent/handler.event-filter.test.ts +50 -0
  19. package/src/agent/handler.ts +147 -145
  20. package/src/channel.config.test.ts +147 -0
  21. package/src/channel.lifecycle.test.ts +234 -0
  22. package/src/channel.ts +90 -140
  23. package/src/config/accounts.resolve.test.ts +38 -0
  24. package/src/config/accounts.ts +257 -22
  25. package/src/config/index.ts +6 -0
  26. package/src/config/network.ts +9 -5
  27. package/src/config/routing.test.ts +88 -0
  28. package/src/config/routing.ts +26 -0
  29. package/src/config/schema.ts +35 -4
  30. package/src/config-schema.ts +5 -41
  31. package/src/dynamic-agent.account-scope.test.ts +17 -0
  32. package/src/dynamic-agent.ts +13 -13
  33. package/src/gateway-monitor.ts +200 -0
  34. package/src/http.ts +16 -2
  35. package/src/media.test.ts +28 -1
  36. package/src/media.ts +59 -1
  37. package/src/monitor/state.queue.test.ts +1 -1
  38. package/src/monitor/state.ts +1 -1
  39. package/src/monitor/types.ts +1 -1
  40. package/src/monitor.active.test.ts +13 -7
  41. package/src/monitor.inbound-filter.test.ts +63 -0
  42. package/src/monitor.ts +948 -128
  43. package/src/monitor.webhook.test.ts +288 -3
  44. package/src/outbound.test.ts +130 -0
  45. package/src/outbound.ts +44 -9
  46. package/src/shared/command-auth.ts +4 -2
  47. package/src/shared/xml-parser.test.ts +21 -1
  48. package/src/shared/xml-parser.ts +18 -0
  49. package/src/types/account.ts +43 -14
  50. package/src/types/config.ts +37 -2
  51. package/src/types/index.ts +3 -0
  52. package/src/types.ts +29 -147
  53. package/GEMINI.md +0 -76
  54. package//345/212/250/346/200/201Agent/350/267/257/347/224/261.md +0 -360
@@ -1,360 +0,0 @@
1
- # 动态 Agent 路由实施方案
2
-
3
- 参考 `openclaw-plugin-wecom/dynamic-agent.js` 实现,为 `@yanhaidao/wecom` 添加按用户/群隔离的动态 Agent 功能。
4
-
5
- ## 1. 目标
6
-
7
- - 每个用户/群组使用独立的 Agent 实例
8
- - 自动将动态 Agent 添加到 `agents.list`
9
- - 完全向后兼容(默认关闭)
10
-
11
- ## 2. 配置设计
12
-
13
- ### 2.1 类型定义 (src/types/config.ts)
14
-
15
- ```typescript
16
- /** 动态 Agent 配置 */
17
- export type WecomDynamicAgentsConfig = {
18
- /** 是否启用动态 Agent */
19
- enabled?: boolean;
20
- /** 私聊:是否为每个用户创建独立 Agent */
21
- dmCreateAgent?: boolean;
22
- /** 群聊:是否启用动态 Agent */
23
- groupEnabled?: boolean;
24
- /** 管理员列表(绕过动态路由,使用主 Agent) */
25
- adminUsers?: string[];
26
- };
27
-
28
- export type WecomConfig = {
29
- enabled?: boolean;
30
- bot?: WecomBotConfig;
31
- agent?: WecomAgentConfig;
32
- media?: WecomMediaConfig;
33
- network?: WecomNetworkConfig;
34
- dynamicAgents?: WecomDynamicAgentsConfig; // 新增
35
- };
36
- ```
37
-
38
- ### 2.2 Schema 定义 (src/config/schema.ts)
39
-
40
- ```typescript
41
- const dynamicAgentsSchema = z.object({
42
- enabled: z.boolean().optional(),
43
- dmCreateAgent: z.boolean().optional(),
44
- groupEnabled: z.boolean().optional(),
45
- adminUsers: z.array(z.string()).optional(),
46
- }).optional();
47
-
48
- export const WecomConfigSchema = z.object({
49
- enabled: z.boolean().optional(),
50
- bot: botSchema,
51
- agent: agentSchema,
52
- media: mediaSchema,
53
- network: networkSchema,
54
- dynamicAgents: dynamicAgentsSchema, // 新增
55
- });
56
- ```
57
-
58
- ## 3. 核心实现 (src/dynamic-agent.ts)
59
-
60
- ```typescript
61
- import type { OpenClawConfig } from "openclaw/plugin-sdk";
62
-
63
- export interface DynamicAgentConfig {
64
- enabled: boolean;
65
- dmCreateAgent: boolean;
66
- groupEnabled: boolean;
67
- adminUsers: string[];
68
- }
69
-
70
- /**
71
- * 读取动态 Agent 配置(带默认值)
72
- */
73
- export function getDynamicAgentConfig(config: OpenClawConfig): DynamicAgentConfig {
74
- const dynamicAgents = (config as any)?.channels?.wecom?.dynamicAgents;
75
- return {
76
- enabled: dynamicAgents?.enabled ?? false,
77
- dmCreateAgent: dynamicAgents?.dmCreateAgent ?? true,
78
- groupEnabled: dynamicAgents?.groupEnabled ?? true,
79
- adminUsers: dynamicAgents?.adminUsers ?? [],
80
- };
81
- }
82
-
83
- /**
84
- * 生成动态 Agent ID
85
- * 算法:wecom-{type}-{sanitizedPeerId}
86
- * type: dm | group
87
- */
88
- export function generateAgentId(chatType: "dm" | "group", peerId: string): string {
89
- const sanitized = String(peerId)
90
- .toLowerCase()
91
- .replace(/[^a-z0-9_-]/g, "_");
92
- return `wecom-${chatType}-${sanitized}`;
93
- }
94
-
95
- /**
96
- * 检查是否应该使用动态 Agent
97
- */
98
- export function shouldUseDynamicAgent(params: {
99
- chatType: "dm" | "group";
100
- senderId: string;
101
- config: OpenClawConfig;
102
- }): boolean {
103
- const { chatType, senderId, config } = params;
104
- const dynamicConfig = getDynamicAgentConfig(config);
105
-
106
- if (!dynamicConfig.enabled) {
107
- return false;
108
- }
109
-
110
- // 管理员绕过动态路由
111
- const sender = String(senderId).trim().toLowerCase();
112
- const isAdmin = dynamicConfig.adminUsers.some(
113
- admin => admin.trim().toLowerCase() === sender
114
- );
115
- if (isAdmin) {
116
- return false;
117
- }
118
-
119
- if (chatType === "group") {
120
- return dynamicConfig.groupEnabled;
121
- }
122
- return dynamicConfig.dmCreateAgent;
123
- }
124
-
125
- /**
126
- * 内存中已确保的 Agent ID(避免重复写入)
127
- */
128
- const ensuredDynamicAgentIds = new Set<string>();
129
-
130
- /**
131
- * 写入队列(避免并发冲突)
132
- */
133
- let ensureDynamicAgentWriteQueue: Promise<void> = Promise.resolve();
134
-
135
- /**
136
- * 将动态 Agent 添加到 agents.list
137
- */
138
- export async function ensureDynamicAgentListed(
139
- agentId: string,
140
- runtime: { config?: { loadConfig?: () => any; writeConfigFile?: (cfg: any) => Promise<void> } }
141
- ): Promise<void> {
142
- const normalizedId = String(agentId).trim().toLowerCase();
143
- if (!normalizedId) return;
144
- if (ensuredDynamicAgentIds.has(normalizedId)) return;
145
-
146
- const configRuntime = runtime?.config;
147
- if (!configRuntime?.loadConfig || !configRuntime?.writeConfigFile) return;
148
-
149
- ensureDynamicAgentWriteQueue = ensureDynamicAgentWriteQueue
150
- .then(async () => {
151
- if (ensuredDynamicAgentIds.has(normalizedId)) return;
152
-
153
- const latestConfig = configRuntime.loadConfig!();
154
- if (!latestConfig || typeof latestConfig !== "object") return;
155
-
156
- const changed = upsertAgentIdOnlyEntry(latestConfig, normalizedId);
157
- if (changed) {
158
- await configRuntime.writeConfigFile!(latestConfig);
159
- console.log(`[wecom] 动态 Agent 已添加: ${normalizedId}`);
160
- }
161
-
162
- ensuredDynamicAgentIds.add(normalizedId);
163
- })
164
- .catch((err) => {
165
- console.warn(`[wecom] 动态 Agent 添加失败: ${normalizedId}`, err);
166
- });
167
-
168
- await ensureDynamicAgentWriteQueue;
169
- }
170
-
171
- /**
172
- * 将 Agent ID 插入 agents.list(如果不存在)
173
- */
174
- function upsertAgentIdOnlyEntry(cfg: any, agentId: string): boolean {
175
- if (!cfg.agents || typeof cfg.agents !== "object") {
176
- cfg.agents = {};
177
- }
178
-
179
- const currentList: Array<{ id: string }> = Array.isArray(cfg.agents.list) ? cfg.agents.list : [];
180
- const existingIds = new Set(
181
- currentList
182
- .map((entry) => entry?.id?.trim().toLowerCase())
183
- .filter(Boolean)
184
- );
185
-
186
- let changed = false;
187
- const nextList = [...currentList];
188
-
189
- // 首次创建时保留 main 作为默认
190
- if (nextList.length === 0) {
191
- nextList.push({ id: "main" });
192
- existingIds.add("main");
193
- changed = true;
194
- }
195
-
196
- if (!existingIds.has(agentId.toLowerCase())) {
197
- nextList.push({ id: agentId });
198
- changed = true;
199
- }
200
-
201
- if (changed) {
202
- cfg.agents.list = nextList;
203
- }
204
-
205
- return changed;
206
- }
207
- ```
208
-
209
- ## 4. 路由拦截点修改
210
-
211
- ### 4.1 Bot 模式 (src/monitor.ts)
212
-
213
- 在 `startAgentForStream` 函数中,路由解析后注入动态 Agent:
214
-
215
- ```typescript
216
- // 约第 923 行,路由解析后
217
- const route = core.channel.routing.resolveAgentRoute({
218
- cfg: config,
219
- channel: "wecom",
220
- accountId: account.accountId,
221
- peer: { kind: chatType === "group" ? "group" : "dm", id: chatId },
222
- });
223
-
224
- // ===== 动态 Agent 注入开始 =====
225
- import { shouldUseDynamicAgent, generateAgentId, ensureDynamicAgentListed } from "./dynamic-agent.js";
226
-
227
- const useDynamicAgent = shouldUseDynamicAgent({
228
- chatType: chatType === "group" ? "group" : "dm",
229
- senderId: userid,
230
- config,
231
- });
232
-
233
- if (useDynamicAgent) {
234
- const targetAgentId = generateAgentId(
235
- chatType === "group" ? "group" : "dm",
236
- chatId
237
- );
238
-
239
- // 覆盖路由
240
- route.agentId = targetAgentId;
241
- route.sessionKey = `agent:${targetAgentId}:${chatType === "group" ? "group" : "dm"}:${chatId}`;
242
-
243
- // 异步添加到 agents.list(不阻塞)
244
- ensureDynamicAgentListed(targetAgentId, core).catch(() => {});
245
- }
246
- // ===== 动态 Agent 注入结束 =====
247
- ```
248
-
249
- ### 4.2 Agent 模式 (src/agent/handler.ts)
250
-
251
- 在 `processAgentMessage` 函数中,路由解析后注入动态 Agent:
252
-
253
- ```typescript
254
- // 约第 438 行,路由解析后
255
- const route = core.channel.routing.resolveAgentRoute({
256
- cfg: config,
257
- channel: "wecom",
258
- accountId: agent.accountId,
259
- peer: { kind: isGroup ? "group" : "dm", id: peerId },
260
- });
261
-
262
- // ===== 动态 Agent 注入开始 =====
263
- import { shouldUseDynamicAgent, generateAgentId, ensureDynamicAgentListed } from "../dynamic-agent.js";
264
-
265
- const useDynamicAgent = shouldUseDynamicAgent({
266
- chatType: isGroup ? "group" : "dm",
267
- senderId: fromUser,
268
- config,
269
- });
270
-
271
- if (useDynamicAgent) {
272
- const targetAgentId = generateAgentId(
273
- isGroup ? "group" : "dm",
274
- peerId
275
- );
276
-
277
- // 覆盖路由
278
- route.agentId = targetAgentId;
279
- route.sessionKey = `agent:${targetAgentId}:${isGroup ? "group" : "dm"}:${peerId}`;
280
-
281
- // 异步添加到 agents.list
282
- ensureDynamicAgentListed(targetAgentId, core).catch(() => {});
283
- }
284
- // ===== 动态 Agent 注入结束 =====
285
- ```
286
-
287
- ## 5. 配置示例
288
-
289
- ```bash
290
- # 启用动态 Agent
291
- openclaw config set channels.wecom.dynamicAgents.enabled true
292
-
293
- # 私聊为每个用户创建独立 Agent(默认 true)
294
- openclaw config set channels.wecom.dynamicAgents.dmCreateAgent true
295
-
296
- # 群聊启用动态 Agent(默认 true)
297
- openclaw config set channels.wecom.dynamicAgents.groupEnabled true
298
-
299
- # 设置管理员(管理员使用主 Agent)
300
- openclaw config set channels.wecom.dynamicAgents.adminUsers '["admin1","admin2"]'
301
- ```
302
-
303
- 生成的配置结构:
304
-
305
- ```json
306
- {
307
- "channels": {
308
- "wecom": {
309
- "enabled": true,
310
- "bot": { ... },
311
- "agent": { ... },
312
- "dynamicAgents": {
313
- "enabled": true,
314
- "dmCreateAgent": true,
315
- "groupEnabled": true,
316
- "adminUsers": ["admin1"]
317
- }
318
- }
319
- },
320
- "agents": {
321
- "list": [
322
- { "id": "main" },
323
- { "id": "wecom-dm-zhangsan" },
324
- { "id": "wecom-group-wr123456" }
325
- ]
326
- }
327
- }
328
- ```
329
-
330
- ## 6. Agent ID 生成规则
331
-
332
- | 场景 | Peer ID | 生成的 Agent ID |
333
- |------|---------|-----------------|
334
- | 私聊 | zhangsan | `wecom-dm-zhangsan` |
335
- | 群聊 | wr123456 | `wecom-group-wr123456` |
336
- | 特殊字符 | zhang.san | `wecom-dm-zhang_san` |
337
- | 大写 | ZhangSan | `wecom-dm-zhangsan` |
338
-
339
- ## 7. 实现步骤
340
-
341
- 1. **新增文件**
342
- - `src/dynamic-agent.ts` - 核心逻辑
343
-
344
- 2. **修改文件**
345
- - `src/types/config.ts` - 添加 `WecomDynamicAgentsConfig` 类型
346
- - `src/config/schema.ts` - 添加 `dynamicAgentsSchema`
347
- - `src/monitor.ts` - Bot 模式路由拦截
348
- - `src/agent/handler.ts` - Agent 模式路由拦截
349
-
350
- 3. **测试验证**
351
- - 未启用时行为不变
352
- - 启用后每个用户有独立会话
353
- - 管理员正确使用主 Agent
354
- - 自动写入 agents.list
355
-
356
- ## 8. 向后兼容性
357
-
358
- - `dynamicAgents.enabled` 默认为 `false`,不启用功能
359
- - 未配置时保持原有行为(所有用户使用同一 Agent)
360
- - 管理员可继续使用 `main` Agent