@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.
- package/.github/workflows/release.yml +56 -0
- package/CLAUDE.md +1 -1
- package/GOVERNANCE.md +26 -0
- package/LICENSE +7 -0
- package/README.md +275 -91
- package/assets/01.bot-add.png +0 -0
- package/assets/01.bot-setp2.png +0 -0
- package/assets/02.agent.add.png +0 -0
- package/assets/02.agent.api-set.png +0 -0
- package/assets/register.png +0 -0
- package/changelog/v2.2.28.md +70 -0
- package/changelog/v2.3.2.md +70 -0
- package/compat-single-account.md +118 -0
- package/package.json +10 -2
- package/src/accounts.ts +17 -55
- package/src/agent/api-client.ts +84 -37
- package/src/agent/api-client.upload.test.ts +110 -0
- package/src/agent/handler.event-filter.test.ts +50 -0
- package/src/agent/handler.ts +147 -145
- package/src/channel.config.test.ts +147 -0
- package/src/channel.lifecycle.test.ts +234 -0
- package/src/channel.ts +90 -140
- package/src/config/accounts.resolve.test.ts +38 -0
- package/src/config/accounts.ts +257 -22
- package/src/config/index.ts +6 -0
- package/src/config/network.ts +9 -5
- package/src/config/routing.test.ts +88 -0
- package/src/config/routing.ts +26 -0
- package/src/config/schema.ts +35 -4
- package/src/config-schema.ts +5 -41
- package/src/dynamic-agent.account-scope.test.ts +17 -0
- package/src/dynamic-agent.ts +13 -13
- package/src/gateway-monitor.ts +200 -0
- package/src/http.ts +16 -2
- package/src/media.test.ts +28 -1
- package/src/media.ts +59 -1
- package/src/monitor/state.queue.test.ts +1 -1
- package/src/monitor/state.ts +1 -1
- package/src/monitor/types.ts +1 -1
- package/src/monitor.active.test.ts +13 -7
- package/src/monitor.inbound-filter.test.ts +63 -0
- package/src/monitor.ts +948 -128
- package/src/monitor.webhook.test.ts +288 -3
- package/src/outbound.test.ts +130 -0
- package/src/outbound.ts +44 -9
- package/src/shared/command-auth.ts +4 -2
- package/src/shared/xml-parser.test.ts +21 -1
- package/src/shared/xml-parser.ts +18 -0
- package/src/types/account.ts +43 -14
- package/src/types/config.ts +37 -2
- package/src/types/index.ts +3 -0
- package/src/types.ts +29 -147
- package/GEMINI.md +0 -76
- 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
|