@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
@@ -2,7 +2,13 @@
2
2
  * WeCom 账号类型定义
3
3
  */
4
4
 
5
- import type { WecomBotConfig, WecomAgentConfig, WecomDmConfig, WecomNetworkConfig } from "./config.js";
5
+ import type {
6
+ WecomBotConfig,
7
+ WecomAgentConfig,
8
+ WecomDmConfig,
9
+ WecomNetworkConfig,
10
+ WecomAccountConfig,
11
+ } from "./config.js";
6
12
 
7
13
  /**
8
14
  * 解析后的 Bot 账号
@@ -40,8 +46,8 @@ export type ResolvedAgentAccount = {
40
46
  corpId: string;
41
47
  /** 应用 Secret */
42
48
  corpSecret: string;
43
- /** 应用 ID (数字) */
44
- agentId: number;
49
+ /** 应用 ID (数字,可选) */
50
+ agentId?: number;
45
51
  /** 回调 Token */
46
52
  token: string;
47
53
  /** 回调加密密钥 */
@@ -52,23 +58,46 @@ export type ResolvedAgentAccount = {
52
58
  network?: WecomNetworkConfig;
53
59
  };
54
60
 
55
- /**
56
- * 已解析的模式状态
57
- */
58
- export type ResolvedMode = {
59
- /** Bot 模式是否已配置 */
60
- bot: boolean;
61
- /** Agent 模式是否已配置 */
62
- agent: boolean;
61
+ /** Matrix/Legacy 的统一账号解析结果 */
62
+ export type ResolvedWecomAccount = {
63
+ /** 账号 ID(用于 bindings.match.accountId) */
64
+ accountId: string;
65
+ /** 展示名称 */
66
+ name?: string;
67
+ /** 是否启用 */
68
+ enabled: boolean;
69
+ /** 是否具备至少一种可用能力(bot/agent) */
70
+ configured: boolean;
71
+ /** 原始账号配置(Matrix 条目或 Legacy 聚合) */
72
+ config: WecomAccountConfig;
73
+ /** Bot 能力 */
74
+ bot?: ResolvedBotAccount;
75
+ /** Agent 能力 */
76
+ agent?: ResolvedAgentAccount;
63
77
  };
64
78
 
79
+ /** 解析模式 */
80
+ export type ResolvedMode = "disabled" | "legacy" | "matrix";
81
+
65
82
  /**
66
- * 解析后的 WeCom 账号集合
83
+ * 已解析的模式状态
67
84
  */
68
85
  export type ResolvedWecomAccounts = {
69
- /** Bot 模式账号 */
86
+ /** 当前模式 */
87
+ mode: ResolvedMode;
88
+ /** 默认账号 ID */
89
+ defaultAccountId: string;
90
+ /** 账号集合(Legacy 下仅 default) */
91
+ accounts: Record<string, ResolvedWecomAccount>;
92
+ /**
93
+ * 向后兼容:默认账号的 bot(历史调用点仍可读取)。
94
+ * Matrix 下等价于 defaultAccountId 对应账号的 bot。
95
+ */
70
96
  bot?: ResolvedBotAccount;
71
- /** Agent 模式账号 */
97
+ /**
98
+ * 向后兼容:默认账号的 agent(历史调用点仍可读取)。
99
+ * Matrix 下等价于 defaultAccountId 对应账号的 agent。
100
+ */
72
101
  agent?: ResolvedAgentAccount;
73
102
  };
74
103
 
@@ -30,15 +30,33 @@ export type WecomNetworkConfig = {
30
30
  egressProxyUrl?: string;
31
31
  };
32
32
 
33
+ /** 路由行为配置 */
34
+ export type WecomRoutingConfig = {
35
+ /**
36
+ * 当路由未命中 bindings(matchedBy=default)时是否拒绝继续处理。
37
+ * - true: fail-closed(推荐于多账号)
38
+ * - false: 允许回退默认 agent(历史兼容)
39
+ */
40
+ failClosedOnDefaultRoute?: boolean;
41
+ };
42
+
33
43
  /**
34
44
  * Bot 模式配置 (智能体)
35
45
  * 用于接收 JSON 格式回调 + 流式回复
36
46
  */
37
47
  export type WecomBotConfig = {
48
+ /** 智能机器人 ID(用于 Matrix 模式二次身份确认) */
49
+ aibotid?: string;
38
50
  /** 回调 Token (企微后台生成) */
39
51
  token: string;
40
52
  /** 回调加密密钥 (企微后台生成) */
41
53
  encodingAESKey: string;
54
+ /**
55
+ * BotId 列表(可选,用于审计与告警)。
56
+ * - 回调路由优先由 URL + 签名决定;botIds 不参与强制拦截。
57
+ * - 当解密后的 aibotid 不在 botIds 中时,仅记录告警日志。
58
+ */
59
+ botIds?: string[];
42
60
  /** 接收者 ID (可选,用于解密校验) */
43
61
  receiveId?: string;
44
62
  /** 流式消息占位符 */
@@ -58,8 +76,8 @@ export type WecomAgentConfig = {
58
76
  corpId: string;
59
77
  /** 应用 Secret */
60
78
  corpSecret: string;
61
- /** 应用 ID */
62
- agentId: number | string;
79
+ /** 应用 ID(可选;不填时可接收回调,但主动发送需具备该字段) */
80
+ agentId?: number | string;
63
81
  /** 回调 Token (企微后台「设置API接收」) */
64
82
  token: string;
65
83
  /** 回调加密密钥 (企微后台「设置API接收」) */
@@ -93,10 +111,27 @@ export type WecomConfig = {
93
111
  bot?: WecomBotConfig;
94
112
  /** Agent 模式配置 (自建应用) */
95
113
  agent?: WecomAgentConfig;
114
+ /**
115
+ * 多账号配置(每个账号可包含 bot + agent,作为一组)。
116
+ * accountId 用于与 OpenClaw `bindings[].match.accountId` 对齐,从而把不同 WeCom 账号路由到不同 OpenClaw agent。
117
+ */
118
+ accounts?: Record<string, WecomAccountConfig>;
119
+ /** 默认账号(可选) */
120
+ defaultAccount?: string;
96
121
  /** 媒体处理配置 */
97
122
  media?: WecomMediaConfig;
98
123
  /** 网络配置 */
99
124
  network?: WecomNetworkConfig;
125
+ /** 路由配置 */
126
+ routing?: WecomRoutingConfig;
100
127
  /** 动态 Agent 配置 */
101
128
  dynamicAgents?: WecomDynamicAgentsConfig;
102
129
  };
130
+
131
+ /** Matrix 账号条目 */
132
+ export type WecomAccountConfig = {
133
+ enabled?: boolean;
134
+ name?: string;
135
+ bot?: WecomBotConfig;
136
+ agent?: WecomAgentConfig;
137
+ };
@@ -7,9 +7,11 @@ export * from "./constants.js";
7
7
 
8
8
  // 配置类型
9
9
  export type {
10
+ WecomAccountConfig,
10
11
  WecomDmConfig,
11
12
  WecomMediaConfig,
12
13
  WecomNetworkConfig,
14
+ WecomRoutingConfig,
13
15
  WecomBotConfig,
14
16
  WecomAgentConfig,
15
17
  WecomConfig,
@@ -17,6 +19,7 @@ export type {
17
19
 
18
20
  // 账号类型
19
21
  export type {
22
+ ResolvedWecomAccount,
20
23
  ResolvedBotAccount,
21
24
  ResolvedAgentAccount,
22
25
  ResolvedMode,
package/src/types.ts CHANGED
@@ -1,143 +1,34 @@
1
- export type WecomDmConfig = {
2
- policy?: "pairing" | "allowlist" | "open" | "disabled";
3
- allowFrom?: Array<string | number>;
4
- };
5
-
6
- export type WecomAccountConfig = {
7
- name?: string;
8
- enabled?: boolean;
9
-
10
- webhookPath?: string;
11
- token?: string;
12
- encodingAESKey?: string;
13
- receiveId?: string;
14
-
15
- streamPlaceholderContent?: string;
16
-
17
- dm?: WecomDmConfig;
18
- welcomeText?: string;
19
- };
20
-
21
- export type WecomConfig = WecomAccountConfig & {
22
- accounts?: Record<string, WecomAccountConfig>;
23
- defaultAccount?: string;
24
- };
25
-
26
- export type ResolvedWecomAccount = {
27
- accountId: string;
28
- name?: string;
29
- enabled: boolean;
30
- configured: boolean;
31
- token?: string;
32
- encodingAESKey?: string;
33
- receiveId: string;
34
- config: WecomAccountConfig;
35
- };
36
-
37
- export type WecomInboundBase = {
38
- msgid?: string;
39
- aibotid?: string;
40
- chattype?: "single" | "group";
41
- chatid?: string;
42
- response_url?: string;
43
- from?: { userid?: string; corpid?: string };
44
- msgtype?: string;
45
- };
46
-
47
- export type WecomInboundText = WecomInboundBase & {
48
- msgtype: "text";
49
- text?: { content?: string };
50
- quote?: unknown;
51
- };
52
-
53
- export type WecomInboundVoice = WecomInboundBase & {
54
- msgtype: "voice";
55
- voice?: { content?: string };
56
- quote?: unknown;
57
- };
58
-
59
- export type WecomInboundStreamRefresh = WecomInboundBase & {
60
- msgtype: "stream";
61
- stream?: { id?: string };
62
- };
63
-
64
- export type WecomInboundEvent = WecomInboundBase & {
65
- msgtype: "event";
66
- create_time?: number;
67
- event?: {
68
- eventtype?: string;
69
- [key: string]: unknown;
70
- };
71
- };
72
-
73
- export type WecomInboundQuote = {
74
- msgtype?: "text" | "image" | "mixed" | "voice" | "file";
75
- text?: { content?: string };
76
- image?: { url?: string };
77
- mixed?: {
78
- msg_item?: Array<{
79
- msgtype: "text" | "image";
80
- text?: { content?: string };
81
- image?: { url?: string };
82
- }>;
83
- };
84
- voice?: { content?: string };
85
- file?: { url?: string };
86
- };
87
-
88
- export type WecomInboundMessage =
89
- | (WecomInboundText & { quote?: WecomInboundQuote })
90
- | WecomInboundVoice
91
- | WecomInboundStreamRefresh
92
- | WecomInboundEvent
93
- | (WecomInboundBase & { quote?: WecomInboundQuote } & Record<string, unknown>);
94
-
95
- export type WecomTemplateCard = {
96
- card_type: "text_notice" | "news_notice" | "button_interaction" | "vote_interaction" | "multiple_interaction";
97
- source?: { icon_url?: string; desc?: string; desc_color?: number };
98
- main_title?: { title?: string; desc?: string };
99
- task_id?: string;
100
- button_list?: Array<{ text: string; style?: number; key: string }>;
101
- sub_title_text?: string;
102
- horizontal_content_list?: Array<{ keyname: string; value?: string; type?: number; url?: string; userid?: string }>;
103
- card_action?: { type: number; url?: string; appid?: string; pagepath?: string };
104
- action_menu?: { desc: string; action_list: Array<{ text: string; key: string }> };
105
- select_list?: Array<{
106
- question_key: string;
107
- title?: string;
108
- selected_id?: string;
109
- option_list: Array<{ id: string; text: string }>;
110
- }>;
111
- submit_button?: { text: string; key: string };
112
- checkbox?: {
113
- question_key: string;
114
- option_list: Array<{ id: string; text: string; is_checked?: boolean }>;
115
- mode?: number;
116
- };
117
- };
118
-
119
- export type WecomInboundTemplateCardEvent = WecomInboundBase & {
120
- msgtype: "event";
121
- event: {
122
- eventtype: "template_card_event";
123
- template_card_event: {
124
- card_type: string;
125
- event_key: string;
126
- task_id: string;
127
- selected_items?: {
128
- selected_item: Array<{
129
- question_key: string;
130
- option_ids: { option_id: string[] };
131
- }>;
132
- };
133
- };
134
- };
135
- };
136
-
137
-
138
1
  /**
139
- * Template card event payload (button click, checkbox, select)
2
+ * Backward-compatible type bridge.
3
+ * Canonical definitions live in `src/types/*`.
140
4
  */
5
+ export type {
6
+ WecomDmConfig,
7
+ WecomAccountConfig,
8
+ WecomConfig,
9
+ ResolvedWecomAccount,
10
+ WecomInboundQuote,
11
+ WecomTemplateCard,
12
+ WecomOutboundMessage,
13
+ } from "./types/index.js";
14
+
15
+ import type {
16
+ WecomBotInboundBase,
17
+ WecomBotInboundText,
18
+ WecomBotInboundVoice,
19
+ WecomBotInboundStreamRefresh,
20
+ WecomBotInboundEvent,
21
+ WecomBotInboundMessage,
22
+ } from "./types/index.js";
23
+
24
+ export type WecomInboundBase = WecomBotInboundBase;
25
+ export type WecomInboundText = WecomBotInboundText;
26
+ export type WecomInboundVoice = WecomBotInboundVoice;
27
+ export type WecomInboundStreamRefresh = WecomBotInboundStreamRefresh;
28
+ export type WecomInboundEvent = WecomBotInboundEvent;
29
+ export type WecomInboundMessage = WecomBotInboundMessage;
30
+
31
+ export type WecomInboundTemplateCardEvent = WecomBotInboundEvent;
141
32
  export type WecomTemplateCardEventPayload = {
142
33
  card_type: string;
143
34
  event_key: string;
@@ -148,12 +39,3 @@ export type WecomTemplateCardEventPayload = {
148
39
  option_ids?: string[];
149
40
  };
150
41
  };
151
-
152
- /**
153
- * Outbound message types that can be sent via response_url
154
- */
155
- export type WecomOutboundMessage =
156
- | { msgtype: "text"; text: { content: string } }
157
- | { msgtype: "markdown"; markdown: { content: string } }
158
- | { msgtype: "template_card"; template_card: WecomTemplateCard };
159
-
package/GEMINI.md DELETED
@@ -1,76 +0,0 @@
1
- # 企业微信 (WeCom) 插件上下文
2
-
3
- ## 平台架构:机器人 (Bot) vs 自建应用 (Agent)
4
-
5
- 本插件采用 **双模 (Dual-Mode)** 架构,结合了企业微信“智能机器人”和“自建应用”的优势。
6
-
7
- ### 1. 定义与边界
8
-
9
- | 特性 | **Bot (智能机器人)** | **Agent (自建应用)** |
10
- | :--- | :--- | :--- |
11
- | **身份** | 虚拟用户/助手。 | 工作台中的服务应用。 |
12
- | **位置** | 存在于 **会话** 中 (单聊或群聊)。 | 存在于 **工作台** 或应用列表。 |
13
- | **协议** | **JSON** (加密)。 | **XML** (加密)。 |
14
- | **交互** | 对话式 (回复用户)。 | 事务式 (通知用户/系统)。 |
15
- | **流式 (Stream)** | ✅ **支持** (打字机效果)。 | ❌ 不支持。 |
16
- | **文件能力** | ❌ **受限** (被动回复无法发文件)。 | ✅ **完整支持** (API 发送视频、文件、图片)。 |
17
- | **群聊支持** | ✅ 原生支持 (可被 @)。 | ⚠️ 受限 (仅能在自建群或通过 API 推送)。 |
18
-
19
- ### 2. 通信协议
20
-
21
- #### A. Bot (智能机器人)
22
- * **接收 (回调)**:
23
- * **格式**: JSON。
24
- * **单聊**: `chattype: "single"`. 无 `chatid`。`cid` = `from.userid`。
25
- * **群聊**: `chattype: "group"`. 有 `chatid`。`cid` = `chatid`。
26
- * **发送 (回复)**:
27
- * **交互回复**: 使用回调中的 `response_url`。支持 `markdown`, `template_card`。**不支持文件/视频**。
28
- * **主动推送**: 使用 Webhook Key。
29
-
30
- #### B. Agent (自建应用)
31
- * **接收 (回调)**:
32
- * **格式**: XML。
33
- * **结构**: `<ToUserName>`, `<FromUserName>`, `<MsgType>`, `<Content>`。
34
- * **发送 (推送)**:
35
- * **API**: `message/send`。
36
- * **能力**: 支持所有媒体类型 (文件, 视频, 语音, 文本, 卡片)。
37
- * **对象**: 用户 (`touser`), 部门 (`toparty`), 标签 (`totag`)。
38
-
39
- ---
40
-
41
- ## 核心策略:机器人优先,智能体兜底 (接近 6 分钟切换)
42
-
43
- ### 目标
44
- * **默认**: 用户在 Bot 会话触发,结果在 Bot 会话内完成回复(含流式)。
45
- * **兜底**: 当 Bot 因超时(接近 6 分钟限制)、异常或流式窗口结束无法完成交付时,若配置了 Agent,则由 Agent 私信触发用户发送最终结果。
46
-
47
- ### 关键约束
48
- * **6 分钟窗口**: 企业微信智能机器人流式链路最多维持 6 分钟。超过后连接断开。
49
-
50
- ### 流程详解
51
-
52
- #### 1. 启动阶段 (Bot)
53
- 1. **接收消息**: 解析 JSON 回调。
54
- 2. **生成 StreamID**: 确定性生成 `streamId = hash(accountId + aibotid + msgid)`,防止并发重试导致重复创建流。
55
- 3. **快速响应**: 立即返回“已接收,处理中”或流式首包,建立连接。
56
-
57
- #### 2. 执行阶段 (Worker)
58
- 4. **异步处理**: 任务入队执行。
59
- 5. **流式刷新**: 当收到企微的“流式消息刷新”回调时,根据 `streamId` 返回最新生成的内容。
60
-
61
- #### 3. 正常交付 (Bot)
62
- 6. **完成**: 任务在 6 分钟内完成,发送 `finish=true` 信号结束流式消息。
63
-
64
- #### 4. 异常切换 (Agent 兜底)
65
- 7. **触发条件**:
66
- * **超时临界**: `now >= createdAt + 6min - 安全阈值(30s)`。
67
- * **链路中断**: Bot 刷新回调长时间未到达 (如 > 60s)。
68
- * **发送异常**: Bot 接口报错。
69
- 8. **切换动作**:
70
- * **状态标记**: 将任务标记为 `agent_fallback`。
71
- * **用户提示**: (可选) Bot 在群内尝试发送“结果将私信送达”。
72
- * **私信推送**: Agent 调用 `message/send` 接口,将最终结果(文本/文件)推送给触发者 `userId`。
73
- * **注意**: 即使原会话是群聊,兜底结果通常通过 Agent **私信** 发送给触发者,因为 Agent 难以直接向普通群推送消息。
74
-
75
- ### 总结
76
- 该策略确保了用户体验(首选流式)和交付可靠性(超时/发文件走 Agent)的平衡。