@dingxiang-me/openclaw-wechat 1.4.1 → 1.7.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 (47) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/README.en.md +89 -12
  3. package/README.md +103 -15
  4. package/docs/channels/wecom.md +28 -3
  5. package/openclaw.plugin.json +467 -10
  6. package/package.json +13 -2
  7. package/src/core/agent-routing.js +6 -0
  8. package/src/core.js +564 -35
  9. package/src/wecom/account-config-core.js +28 -8
  10. package/src/wecom/account-config.js +55 -0
  11. package/src/wecom/account-diagnostics.js +121 -0
  12. package/src/wecom/account-paths.js +39 -0
  13. package/src/wecom/agent-inbound-dispatch.js +9 -0
  14. package/src/wecom/agent-inbound-guards.js +24 -4
  15. package/src/wecom/agent-inbound-processor.js +27 -0
  16. package/src/wecom/agent-webhook-handler.js +11 -0
  17. package/src/wecom/bot-context.js +2 -1
  18. package/src/wecom/bot-dispatch-fallback.js +2 -1
  19. package/src/wecom/bot-inbound-content.js +73 -3
  20. package/src/wecom/bot-inbound-dispatch-runtime.js +2 -1
  21. package/src/wecom/bot-inbound-executor-helpers.js +56 -5
  22. package/src/wecom/bot-inbound-executor.js +19 -0
  23. package/src/wecom/bot-inbound-guards.js +36 -4
  24. package/src/wecom/bot-runtime-context.js +5 -3
  25. package/src/wecom/bot-webhook-dispatch.js +45 -12
  26. package/src/wecom/bot-webhook-handler.js +45 -13
  27. package/src/wecom/command-handlers.js +26 -0
  28. package/src/wecom/command-status-text.js +76 -7
  29. package/src/wecom/observability-metrics.js +133 -0
  30. package/src/wecom/outbound-agent-push.js +2 -1
  31. package/src/wecom/outbound-bot-card.js +103 -0
  32. package/src/wecom/outbound-delivery.js +92 -7
  33. package/src/wecom/outbound-response-delivery.js +10 -6
  34. package/src/wecom/outbound-webhook-delivery.js +42 -1
  35. package/src/wecom/plugin-account-policy-services.js +19 -0
  36. package/src/wecom/plugin-base-services.js +13 -0
  37. package/src/wecom/plugin-constants.js +1 -1
  38. package/src/wecom/plugin-delivery-inbound-services.js +8 -0
  39. package/src/wecom/plugin-processing-deps.js +4 -0
  40. package/src/wecom/plugin-route-runtime-deps.js +5 -0
  41. package/src/wecom/plugin-services.js +7 -0
  42. package/src/wecom/policy-resolvers.js +82 -5
  43. package/src/wecom/register-runtime.js +31 -2
  44. package/src/wecom/route-registration.js +173 -41
  45. package/src/wecom/runtime-utils.js +7 -2
  46. package/src/wecom/webhook-adapter.js +61 -0
  47. package/src/wecom/webhook-bot.js +26 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,125 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ### Added
8
+ - 新增 Bot 卡片回包能力:`channels.wecom.bot.card`(支持 `markdown` / `template_card`)
9
+ - 新增卡片回包分层开关:`bot.card.responseUrlEnabled`、`bot.card.webhookBotEnabled`
10
+ - 新增 `outbound-bot-card` 模块与回归测试,统一卡片 payload 构建与长度截断
11
+ - 新增多账号冲突诊断:启动时输出 `wecom: account diagnosis ...`(重复 callbackToken / bot token、共享路径等)
12
+ - 新增场景化远端 E2E 脚本:`npm run wecom:e2e:scenario -- --scenario <bot-smoke|agent-smoke|full-smoke|bot-queue>`
13
+ - 新增账号路径诊断单测与默认路径单测(Agent/Bot)
14
+ - 新增 sunnoy 风格配置兼容层:支持 `agent.*`、`dynamicAgents.*`、`dm.createAgentOnFirstMessage`
15
+ - 新增 legacy inline account 兼容解析(`channels.wecom.<accountId>`)及对应回归测试
16
+ - 新增 legacy 指令配置兼容:`commandAllowlist`、`commandBlockMessage`
17
+ - 新增 legacy 动态路由模板兼容:顶层 `workspaceTemplate` 自动映射到 `dynamicAgent.workspaceTemplate`
18
+ - 新增 legacy 兼容:`dm.allowFrom` 与 `commands.blockMessage`
19
+ - 新增 legacy `name` 字段兼容(渠道与账户级)
20
+ - 改进命令策略:当配置了 `commands.allowlist`(或 `commandAllowlist`)且未显式关闭时,自动启用命令白名单
21
+ - 修复账户级 `dm.allowFrom` 兼容:`accounts.<id>.dm.allowFrom` 在归一化后保持生效
22
+ - 调整 manifest:`accounts.<id>` 不再强制 `corpId/corpSecret/agentId`,支持 Bot-only 账号配置
23
+ - 新增 Bot 路由兼容:默认新路径会自动注册 legacy alias(`/webhooks/wecom` / `/webhooks/wecom/<id>`),并在与 Agent 路径冲突时自动跳过
24
+ - 新增 Agent 路由兼容:默认新路径会自动注册 legacy alias(`/webhooks/app` / `/webhooks/app/<id>`),并在与 Bot 路径冲突时自动跳过
25
+ - 新增 Agent 自检增强:`wecom:agent:selfcheck` 在默认新路径场景下会额外探测 legacy alias(`/webhooks/app`)
26
+ - 新增远端浏览器 E2E 辅助脚本:`test:e2e:prepare-browser`(沙箱就绪检查)与 `test:e2e:collect-pdf`(回收 PDF 产物)
27
+ - 新增远端 E2E 联动参数:`wecom:remote:e2e` / `wecom:e2e:scenario` 支持 `--prepare-browser`、`--collect-pdf`、`--browser-prepare-mode`、`--browser-require-ready`
28
+ - 新增一键命令:`npm run wecom:e2e:full`(full-smoke + 浏览器准备检查 + PDF 回收)
29
+ - 新增远端 E2E 环境变量兼容:`wecom:remote:e2e` / `wecom:e2e:scenario` 支持 `WECOM_E2E_*` 与 legacy `E2E_WECOM_*` 默认参数注入
30
+ - 新增场景化兼容矩阵:`wecom:e2e:scenario --scenario compat-smoke` 支持新旧回调 URL 双链路验证(Agent/Bot)
31
+ - 新增兼容矩阵快捷命令:`npm run wecom:e2e:compat`(等价 `wecom:e2e:scenario --scenario compat-smoke`)
32
+ - CI 远端联调增强:`workflow_dispatch` 支持选择 `e2e_scenario`、浏览器准备模式与 PDF 回收参数
33
+ - FAQ 补充:明确 Bot 模式在“微信插件入口”可见性的产品限制,并给出 Agent/Bot 并行方案建议
34
+ - 新增远端矩阵 E2E:`test:e2e:matrix`,覆盖 Bot 回调验签、异常请求、stream-refresh 与重复消息去重链路
35
+ - 新增 `matrix-smoke` 场景:`wecom:e2e:scenario --scenario matrix-smoke` 可直接触发 Bot 协议矩阵回归
36
+ - 新增 Bot 可见性诊断:`/status` 与 `wecom:bot:selfcheck` 输出“微信插件入口联系人”提示,避免将产品形态差异误判为插件故障
37
+ - 新增 Bot 自检多账户参数:`npm run wecom:bot:selfcheck -- --account <id>` 与 `--all-accounts`(支持 scoped `WECOM_<ACCOUNT>_BOT_*` 环境变量)
38
+ - 新增安装元数据:`package.json` 增加 `openclaw.install`(`defaultChoice=npm` + `npmSpec=@dingxiang-me/openclaw-wechat`)
39
+ - 新增命令兼容映射:`/new` 与 `/clear` 一样会转换为 `/reset`(Agent/Bot 双模式,支持 `/new` 白名单兼容)
40
+ - 新增 Bot 自检插件加载诊断:输出 `plugins.enabled / plugins.entry.openclaw-wechat / plugins.allow` 三项结果
41
+ - 新增 Bot 自检 URL 验证检查:`e2e.url.verify` 覆盖 `GET echostr` 验签 + 解密回显
42
+ - 新增 Bot 自检本地回调健康诊断:可识别 `html-fallback/route-not-found/gateway-unreachable` 并输出修复提示
43
+
44
+ ### Changed
45
+ - Bot 回包链路增强:当无媒体且卡片开启时,`response_url` / `webhook_bot` 优先发送卡片并自动降级文本
46
+ - 多账号默认回调路径自动分配:
47
+ - Agent:非 default 账户缺省为 `/wecom/<accountId>/callback`
48
+ - Bot:非 default 账户缺省为 `/wecom/<accountId>/bot/callback`
49
+ - `openclaw.plugin.json` 与 README(中英文)补充自动路径与账号体检说明
50
+ - Bot 配置解析增强:在 legacy `agent` 配置块存在时,自动将顶层 `token/encodingAesKey/webhookPath` 识别为 Bot 配置
51
+ - 动态路由增强:支持私聊/群聊维度开关(`dmCreateAgentOnFirstMessage`、`groupEnabled`)
52
+ - 远端 E2E 兼容增强:`test:e2e:remote` 支持 `WECOM_E2E_*` 与 legacy `E2E_WECOM_*` 两套环境变量
53
+ - 语法检查范围增强:`test:syntax` 从仅 `src` 扩展到 `src + scripts + tests`,降低脚本/测试回归漏检风险
54
+
55
+ ## [1.7.2] - 2026-03-05
56
+
57
+ ### Changed
58
+ - Bot 群聊触发策略收紧:在企业微信官方 Bot 模式下,`groupChat.triggerMode=direct/keyword` 会自动规范为 `mention` 并输出告警,避免“配置可写但平台不回调”的误导
59
+ - Bot `/status` 与 `/help` 文案增强:明确“群聊通常仅 @ 机器人消息触发(平台限制)”
60
+ - 文档更新:README 与 `docs/channels/wecom.md` 明确 Bot 群聊触发限制与配置降级行为
61
+
62
+ ### Added
63
+ - 新增回归测试 `tests/wecom-bot-inbound-executor-helpers.test.mjs`,覆盖 Bot 群聊策略规范化
64
+ - 更新命令处理测试,覆盖 Bot 状态文案中的平台限制提示
65
+
66
+ ## [1.7.1] - 2026-03-05
67
+
68
+ ### Added
69
+ - 新增事件策略 `events.*`:支持处理 `MsgType=event`,并支持 `enter_agent` 自动欢迎语
70
+ - 新增事件策略环境变量:`WECOM_EVENTS_*` 与 `WECOM_<ACCOUNT>_EVENTS_*`
71
+ - 新增事件链路测试:覆盖 XML 事件字段解析、事件调度与 `enter_agent` 欢迎语发送
72
+
73
+ ### Changed
74
+ - `/status` 增加事件策略状态展示(是否启用事件处理与欢迎语)
75
+ - 文档更新:README(中英文)与渠道文档新增事件策略配置说明
76
+
77
+ ## [1.7.0] - 2026-03-05
78
+
79
+ ### Added
80
+ - 新增私聊策略 `dm.mode`:支持 `open` / `allowlist` / `deny`,并支持账户级覆盖(`accounts.<id>.dm`)
81
+ - 新增私聊策略环境变量:`WECOM_DM_POLICY|MODE`、`WECOM_DM_ALLOW_FROM`、`WECOM_DM_REJECT_MESSAGE`,并支持 `WECOM_<ACCOUNT>_DM_*` scoped 覆盖
82
+ - 新增 WeCom 可观测指标存储:入站量、回包成功/失败、错误计数与最近失败样本
83
+ - 新增 `/status` 观测输出:展示入站/回包统计、成功率和最近一次失败原因
84
+ - 新增 Bot mixed 入站增强:支持 `file/link/location/voice` 子项解析与文件/语音链路接入
85
+ - 新增测试覆盖:DM 策略解析/拦截、可观测统计、mixed 文件与语音解析、回包指标记录
86
+
87
+ ### Changed
88
+ - Agent/Bot 入站守卫统一接入 DM 策略,按配置执行放行/拒绝
89
+ - 回包编排链路统一埋点(active_stream / response_url / webhook_bot / agent_push)并上报最终状态
90
+ - Bot 入站内容构建从“单类型”提升为“mixed 聚合”流程,支持同一条 mixed 消息中组合文件与语音内容
91
+
92
+ ### Fixed
93
+ - 修复 Bot 入站执行流在新增 DM 策略依赖后的测试注入缺失问题(`resolveWecomDmPolicy is required`)
94
+
95
+ ## [1.6.1] - 2026-03-04
96
+
97
+ ### Added
98
+ - Bot 可见性诊断补齐:`/status` 与 `wecom:bot:selfcheck` 新增“微信插件入口联系人”提示,避免将产品形态差异误判为故障
99
+ - Bot 体检支持账户维度:`npm run wecom:bot:selfcheck -- --account <id>` 与 `--all-accounts`
100
+ - Bot 体检新增插件加载诊断:`plugins.enabled / plugins.entry.openclaw-wechat / plugins.allow`
101
+ - 新增脚本级回归测试:覆盖 Bot 体检多账户发现与参数冲突(`--all-accounts` + `--url`)
102
+
103
+ ### Changed
104
+ - 命令兼容增强:`/new` 与 `/clear` 在 Agent/Bot 双模式统一映射到 `/reset`,并兼容白名单
105
+ - 插件安装元数据完善:`package.json` 增加 `openclaw.install`(`defaultChoice=npm` + `npmSpec=@dingxiang-me/openclaw-wechat`)
106
+
107
+ ## [1.5.0] - 2026-03-04
108
+
109
+ ### Added
110
+ - Bot 模式补齐语音 URL 下载 + 本地转写链路:支持从 `voice.url/media_url/download_url/file_url` 拉取音频并接入本地转写
111
+ - Bot 多账号能力增强:支持 `channels.wecom.accounts.<id>.bot` 独立配置与 `WECOM_<ACCOUNT>_BOT_*` 环境变量覆盖
112
+ - Bot 路由增强:同一路径支持多账户签名匹配并按账户分组注册 webhook
113
+ - Bot 回包增强:支持从回复文本中提取 `/workspace/...` 本地图片并自动打包为 `active_stream msg_item(image)`
114
+ - 新增/扩展回归测试,覆盖 Bot 多账号、语音转写、workspace 图片打包等关键路径(总测试数提升到 264)
115
+
116
+ ### Changed
117
+ - `register-runtime` 启动日志升级:支持展示 Bot 多账户加载状态与 webhook 聚合信息
118
+ - `openclaw.plugin.json` 新增 `accounts.<id>.bot` schema 与敏感字段标记
119
+ - README(中英文)补充多账户 Bot 覆盖配置与环境变量说明
120
+
121
+ ### Fixed
122
+ - 修复 Bot 入站内容构建依赖注入缺失导致的运行时异常(`resolveWecomVoiceTranscriptionConfig is required`)
123
+ - 修复 Bot dispatcher 在多账号场景下因未携带匹配账户配置导致的 401 响应问题
124
+ - 修复路由注册与依赖工厂在多账号 Bot resolver 接入后的兼容性回归
125
+
7
126
  ## [1.4.1] - 2026-03-03
8
127
 
9
128
  ### Added
package/README.en.md CHANGED
@@ -30,11 +30,15 @@ OpenClaw-Wechat is an OpenClaw channel plugin for Enterprise WeChat (WeCom), wit
30
30
  | WeCom inbound message handling | ✅ | text/image/voice/link/file/video (Agent + Bot) |
31
31
  | AI auto-reply via OpenClaw runtime | ✅ | routed by session key |
32
32
  | Native WeCom Bot stream protocol | ✅ | `msgtype=stream` refresh flow |
33
+ | Bot card replies | ✅ | `markdown/template_card` with automatic text fallback |
33
34
  | Multi-account support | ✅ | `channels.wecom.accounts.<id>` |
34
35
  | Sender allowlist and admin bypass | ✅ | `allowFrom` + `adminUsers` |
35
- | Command allowlist | ✅ | `/help`, `/status`, `/clear`, etc. |
36
+ | Direct-message policy | ✅ | `dm.mode=open/allowlist/deny` + account overrides |
37
+ | Event welcome reply (`enter_agent`) | ✅ | configurable via `events.enterAgentWelcome*` |
38
+ | Command allowlist | ✅ | `/help`, `/status`, `/clear`, `/new`, etc. |
36
39
  | Group trigger policy | ✅ | mention-required or direct-trigger |
37
40
  | Debounce and late-reply fallback | ✅ | better stability under queue/timeout |
41
+ | Observability metrics | ✅ | inbound/delivery/error counters + recent failures |
38
42
  | Outbound proxy for WeCom APIs | ✅ | `outboundProxy` / `WECOM_PROXY` |
39
43
 
40
44
  ## Mode Comparison
@@ -93,7 +97,7 @@ openclaw gateway restart
93
97
  openclaw gateway status
94
98
  npm run wecom:selfcheck -- --all-accounts
95
99
  npm run wecom:agent:selfcheck -- --account default
96
- npm run wecom:bot:selfcheck
100
+ npm run wecom:bot:selfcheck -- --account default
97
101
  ```
98
102
 
99
103
  ## Requirements
@@ -137,12 +141,16 @@ openclaw plugins install @dingxiang-me/openclaw-wechat
137
141
  | `agentId` | number/string | - | Agent mode |
138
142
  | `callbackToken` | string | - | sensitive |
139
143
  | `callbackAesKey` | string | - | sensitive |
140
- | `webhookPath` | string | `/wecom/callback` | Agent callback path |
144
+ | `webhookPath` | string | `/wecom/callback` | Agent callback path (auto `/wecom/<accountId>/callback` when non-default account leaves it empty) |
145
+ | `agent` | object | - | legacy layout: `agent.corpId/corpSecret/agentId` (equivalent to top-level Agent fields) |
141
146
  | `outboundProxy` | string | - | WeCom API proxy |
142
147
  | `webhooks` | object | - | named webhook target map (`{ "ops": "https://...key=xxx" }`) |
143
- | `accounts` | object | - | multi-account map |
148
+ | `accounts` | object | - | multi-account map (supports `accounts.<id>.bot` overrides) |
144
149
 
145
- Compatibility note: legacy Agent keys `token` / `encodingAesKey` are still accepted (mapped to `callbackToken` / `callbackAesKey`). Prefer the new names for new configs.
150
+ Compatibility note: legacy keys/layouts are supported: `name`, `token` / `encodingAesKey`, `agent.*`, `dynamicAgents.*`, `dm.createAgentOnFirstMessage`, `dm.allowFrom`, `workspaceTemplate`, `commandAllowlist/commandBlockMessage`, `commands.blockMessage`, and inline account blocks (`channels.wecom.<accountId>`). New configs should prefer `accounts.<id>`, `callbackToken/callbackAesKey`, `commands.*`, and `dynamicAgent.*`.
151
+
152
+ Note: `accounts.<id>` now supports Bot-only accounts (`bot.*` only) and no longer requires `corpId/corpSecret/agentId`.
153
+ Compat note: when default new paths are used, legacy aliases are auto-registered for smoother migration. Agent default paths also add `/webhooks/app` aliases (`/webhooks/app/<id>` for multi-account), and Bot default paths add `/webhooks/wecom` aliases (`/webhooks/wecom/<id>`). Conflicting aliases are skipped with warnings.
146
154
 
147
155
  ### Bot config (`channels.wecom.bot`)
148
156
 
@@ -151,12 +159,44 @@ Compatibility note: legacy Agent keys `token` / `encodingAesKey` are still accep
151
159
  | `enabled` | boolean | `false` | enable Bot mode |
152
160
  | `token` | string | - | sensitive |
153
161
  | `encodingAesKey` | string | - | sensitive, 43 chars |
154
- | `webhookPath` | string | `/wecom/bot/callback` | Bot callback path |
162
+ | `webhookPath` | string | `/wecom/bot/callback` | Bot callback path (auto `/wecom/<accountId>/bot/callback` when non-default account leaves it empty) |
155
163
  | `placeholderText` | string | processing text | stream initial placeholder |
156
164
  | `streamExpireMs` | integer | `600000` | 30s ~ 1h |
157
165
  | `replyTimeoutMs` | integer | `90000` | Bot reply timeout (15s ~ 10m) |
158
166
  | `lateReplyWatchMs` | integer | `180000` | async late-reply watch window |
159
167
  | `lateReplyPollMs` | integer | `2000` | async late-reply poll interval |
168
+ | `card` | object | see below | Bot card reply policy (`response_url` / `webhook_bot`) |
169
+
170
+ #### Bot card config (`channels.wecom.bot.card`)
171
+
172
+ | Key | Type | Default | Notes |
173
+ |---|---|---|---|
174
+ | `enabled` | boolean | `false` | enable card replies |
175
+ | `mode` | string | `markdown` | `markdown` (compat-first) or `template_card` |
176
+ | `title` | string | `OpenClaw-Wechat` | card title |
177
+ | `subtitle` | string | - | card subtitle |
178
+ | `footer` | string | - | card footer text |
179
+ | `maxContentLength` | integer | `1400` | max card body length (auto-truncated) |
180
+ | `responseUrlEnabled` | boolean | `true` | enable card sending at `response_url` layer |
181
+ | `webhookBotEnabled` | boolean | `true` | enable card sending at `webhook_bot` layer |
182
+
183
+ ### Account-level Bot overrides (`channels.wecom.accounts.<id>.bot`)
184
+
185
+ When multi-account is enabled, each account can override Bot callback credentials/path/timeout/proxy. If omitted, it falls back to `channels.wecom.bot`.
186
+
187
+ | Key | Type | Default | Notes |
188
+ |---|---|---|---|
189
+ | `enabled` | boolean | `false` | enable Bot mode for this account |
190
+ | `token` / `callbackToken` | string | - | callback token (legacy alias supported) |
191
+ | `encodingAesKey` / `callbackAesKey` | string | - | callback AES key (legacy alias supported) |
192
+ | `webhookPath` | string | `/wecom/bot/callback` | Bot callback path |
193
+ | `placeholderText` | string | processing text | stream placeholder |
194
+ | `streamExpireMs` | integer | `600000` | stream TTL |
195
+ | `replyTimeoutMs` | integer | `90000` | model reply timeout |
196
+ | `lateReplyWatchMs` | integer | `180000` | late-reply watch window |
197
+ | `lateReplyPollMs` | integer | `2000` | late-reply poll interval |
198
+ | `card` | object | - | account-level card policy (overrides global `bot.card`) |
199
+ | `outboundProxy` / `proxyUrl` / `proxy` | string | - | account-level Bot proxy |
160
200
 
161
201
  ### Policy config
162
202
 
@@ -165,10 +205,13 @@ Compatibility note: legacy Agent keys `token` / `encodingAesKey` are still accep
165
205
  | Sender ACL | `allowFrom`, `allowFromRejectMessage` |
166
206
  | Command ACL | `commands.enabled`, `commands.allowlist`, `commands.rejectMessage` |
167
207
  | Admin bypass | `adminUsers` |
208
+ | Direct-message policy | `dm.mode`, `dm.allowFrom`, `dm.rejectMessage` |
209
+ | Event policy | `events.enabled`, `events.enterAgentWelcomeEnabled`, `events.enterAgentWelcomeText` |
168
210
  | Group trigger | `groupChat.enabled`, `groupChat.triggerMode`, `groupChat.mentionPatterns`, `groupChat.triggerKeywords` |
169
- | Dynamic route | `dynamicAgent.*` (with `workspaceTemplate` bootstrap seeding) |
211
+ | Dynamic route | `dynamicAgent.*` (compatible with `dynamicAgents.*`, `dm.createAgentOnFirstMessage`) |
170
212
  | Debounce | `debounce.enabled`, `debounce.windowMs`, `debounce.maxBatch` |
171
213
  | Agent streaming | `streaming.enabled`, `streaming.minChars`, `streaming.minIntervalMs` |
214
+ | Observability | `observability.enabled`, `observability.logPayloadMeta` |
172
215
 
173
216
  ## Capability Matrix
174
217
 
@@ -203,8 +246,8 @@ Quoted reply context in Bot mode is also supported (`quote` is prepended into cu
203
246
  | `/help` | show help |
204
247
  | `/status` | show runtime status |
205
248
  | `/clear` | clear session (mapped to `/reset`) |
249
+ | `/new` | new session (mapped to `/reset`) |
206
250
  | `/reset` | reset conversation |
207
- | `/new` | new session (runtime-supported) |
208
251
  | `/compact` | compact session (runtime-supported) |
209
252
 
210
253
  Session key policy: default is one-user-one-session (`wecom:<userid>`).
@@ -241,6 +284,16 @@ Outbound target formats:
241
284
  | `WECOM_BOT_REPLY_TIMEOUT_MS` | Bot reply timeout |
242
285
  | `WECOM_BOT_LATE_REPLY_WATCH_MS` | Bot late-reply watch window |
243
286
  | `WECOM_BOT_LATE_REPLY_POLL_MS` | Bot late-reply poll interval |
287
+ | `WECOM_BOT_CARD_ENABLED` | enable Bot card replies |
288
+ | `WECOM_BOT_CARD_MODE` | card mode: `markdown` / `template_card` |
289
+ | `WECOM_BOT_CARD_TITLE` | card title |
290
+ | `WECOM_BOT_CARD_SUBTITLE` | card subtitle |
291
+ | `WECOM_BOT_CARD_FOOTER` | card footer text |
292
+ | `WECOM_BOT_CARD_MAX_CONTENT_LENGTH` | max card body length |
293
+ | `WECOM_BOT_CARD_RESPONSE_URL_ENABLED` | card switch for response_url layer |
294
+ | `WECOM_BOT_CARD_WEBHOOK_BOT_ENABLED` | card switch for webhook_bot layer |
295
+ | `WECOM_<ACCOUNT>_BOT_*` | account-level Bot override (e.g. `WECOM_SALES_BOT_TOKEN`) |
296
+ | `WECOM_<ACCOUNT>_BOT_PROXY` | account-level Bot proxy for media/download/reply |
244
297
 
245
298
  ### Stability and policy
246
299
 
@@ -248,10 +301,13 @@ Outbound target formats:
248
301
  |---|---|
249
302
  | `WECOM_ALLOW_FROM*` | sender authorization |
250
303
  | `WECOM_COMMANDS_*` | command ACL |
304
+ | `WECOM_DM_*`, `WECOM_<ACCOUNT>_DM_*` | DM policy + allowlist |
305
+ | `WECOM_EVENTS_*`, `WECOM_<ACCOUNT>_EVENTS_*` | event handling + enter_agent welcome text |
251
306
  | `WECOM_GROUP_CHAT_*` | group trigger policy |
252
307
  | `WECOM_DEBOUNCE_*` | text debounce |
253
308
  | `WECOM_STREAMING_*` | Agent incremental output |
254
309
  | `WECOM_LATE_REPLY_*` | async late reply fallback |
310
+ | `WECOM_OBSERVABILITY_ENABLED`, `WECOM_OBSERVABILITY_PAYLOAD_META` | observability counters and payload-meta logging |
255
311
  | `WECOM_PROXY`, `WECOM_<ACCOUNT>_PROXY` | outbound proxy |
256
312
 
257
313
  ### Local voice transcription fallback
@@ -279,6 +335,7 @@ See [`docs/troubleshooting/coexistence.md`](./docs/troubleshooting/coexistence.m
279
335
  | Inbound received but no reply | gateway logs + dispatch status | timeout, queueing, policy block |
280
336
  | Bot image parse failed | `wecom(bot): failed to fetch image url` | expired URL/non-image stream |
281
337
  | Voice transcription failed | local command/model path | whisper/ffmpeg environment issue |
338
+ | Startup logs show `wecom: account diagnosis ...` | diagnosis code + account list | multi-account token/agent/path conflict risk |
282
339
  | gettoken failed | WeCom API result | wrong credentials or network/proxy |
283
340
 
284
341
  Useful commands:
@@ -288,7 +345,7 @@ openclaw gateway status
288
345
  openclaw status --deep
289
346
  openclaw logs --follow
290
347
  npm run wecom:selfcheck -- --all-accounts
291
- npm run wecom:bot:selfcheck
348
+ npm run wecom:bot:selfcheck -- --all-accounts
292
349
  ```
293
350
 
294
351
  ## Development
@@ -296,12 +353,22 @@ npm run wecom:bot:selfcheck
296
353
  | Command | Purpose |
297
354
  |---|---|
298
355
  | `npm test` | syntax + tests |
299
- | `WECOM_E2E_ENABLE=1 npm run test:e2e:remote` | run remote E2E tests (skipped by default; requires `WECOM_E2E_BOT_URL`/`WECOM_E2E_AGENT_URL`) |
356
+ | `WECOM_E2E_ENABLE=1 npm run test:e2e:remote` | run remote E2E tests (skipped by default; supports both `WECOM_E2E_*` and legacy `E2E_WECOM_*` env sets) |
357
+ | `WECOM_E2E_MATRIX_ENABLE=1 npm run test:e2e:matrix` | run remote matrix E2E (signature/negative requests/stream refresh/dedupe) |
358
+ | `npm run test:e2e:prepare-browser` | check remote browser sandbox readiness (optional Chromium auto-install) |
359
+ | `npm run test:e2e:collect-pdf` | collect browser-generated PDFs from remote sandbox to local artifacts |
300
360
  | `npm run wecom:selfcheck -- --all-accounts` | config/network self-check |
301
361
  | `npm run wecom:agent:selfcheck -- --account <id>` | Agent E2E self-check (URL verify + encrypted POST) |
302
- | `npm run wecom:bot:selfcheck` | Bot E2E self-check (signature/encryption/stream-refresh) |
362
+ | `npm run wecom:bot:selfcheck -- --account <id>` | Bot E2E self-check (URL verify/signature/encryption/stream-refresh, supports multi-account) |
303
363
  | `npm run wecom:remote:e2e -- --mode all --agent-url <public-agent-callback> --bot-url <public-bot-callback>` | remote matrix verification (Agent + Bot) |
304
- | `GitHub Actions -> CI -> Run workflow (run_remote_e2e=true)` | trigger remote E2E in CI (uses `WECOM_E2E_*` secrets) |
364
+ | `npm run wecom:remote:e2e -- --mode all --agent-url <public-agent-callback> --bot-url <public-bot-callback> --prepare-browser --collect-pdf` | remote matrix with browser sandbox prepare + PDF artifact collection |
365
+ | `WECOM_E2E_BOT_URL=<...> WECOM_E2E_AGENT_URL=<...> npm run wecom:remote:e2e -- --mode all` | env-driven remote E2E (also compatible with legacy `E2E_WECOM_*`) |
366
+ | `npm run wecom:e2e:scenario -- --scenario full-smoke --agent-url <public-agent-callback> --bot-url <public-bot-callback>` | scenario-based E2E (preset smoke/queue workflows) |
367
+ | `npm run wecom:e2e:scenario -- --scenario compat-smoke --agent-url <new-agent-url> --agent-legacy-url <legacy-agent-url> --bot-url <new-bot-url> --bot-legacy-url <legacy-bot-url>` | compatibility matrix run across new + legacy webhook endpoints |
368
+ | `npm run wecom:e2e:scenario -- --scenario matrix-smoke --bot-url <public-bot-callback>` | bot protocol matrix checks (signature/negative requests/stream-refresh/dedupe; requires `WECOM_BOT_TOKEN/WECOM_BOT_ENCODING_AES_KEY`) |
369
+ | `npm run wecom:e2e:compat -- --agent-url <new-agent-url> --agent-legacy-url <legacy-agent-url> --bot-url <new-bot-url> --bot-legacy-url <legacy-bot-url>` | compatibility matrix shortcut command (same as `--scenario compat-smoke`) |
370
+ | `npm run wecom:e2e:full -- --agent-url <public-agent-callback> --bot-url <public-bot-callback>` | one-shot full-smoke (pre-enabled `--prepare-browser --collect-pdf`) |
371
+ | `GitHub Actions -> CI -> Run workflow` | trigger remote E2E in CI with `run_remote_e2e=true`; optionally pick `e2e_scenario` (including `compat-smoke`) and browser options |
305
372
  | `npm run wecom:smoke` | smoke test after upgrades (Agent path) |
306
373
  | `npm run wecom:smoke -- --with-bot-e2e` | smoke test after upgrades (with Bot E2E) |
307
374
  | `openclaw gateway restart` | restart runtime |
@@ -317,6 +384,16 @@ WeCom image URLs can return non-standard content type or encrypted media stream.
317
384
  ### Can Telegram and WeCom affect each other?
318
385
  They are logically independent, but can conflict via shared webhook paths, multi-process gateway races, or loose plugin loading policy.
319
386
 
387
+ ### Why is the Bot contact not visible in the WeChat plugin entry?
388
+ This is usually a WeCom product behavior difference, not a plugin bug.
389
+ In many tenants, the WeChat plugin entry maps to **self-built app (Agent callback)** visibility, while Bot mode (intelligent bot API) is not exposed as a direct contact.
390
+
391
+ Recommended setup:
392
+ 1. Need stable direct entry: prefer Agent mode.
393
+ 2. Need group notifications/conversation: prefer Webhook Bot / Bot mode.
394
+ 3. Need both: run Agent (entry) + Bot (group capability) together.
395
+ 4. Run `npm run wecom:bot:selfcheck -- --account <id>` (or `--all-accounts`) and check `bot.entry.visibility` to confirm this is expected product behavior rather than a plugin fault.
396
+
320
397
  ### Why does `curl https://<domain>/wecom/callback` return WebUI instead of webhook health text?
321
398
  That is a routing issue. `GET /wecom/callback` (without `echostr`) should return plain text `wecom webhook ok`.
322
399
  If you get WebUI HTML, your reverse proxy is sending `/wecom/*` to frontend/static service.
package/README.md CHANGED
@@ -92,12 +92,16 @@ npm run wecom:selfcheck -- --all-accounts
92
92
  | 企业微信入站消息处理 | ✅ | 文本、图片、语音、链接、文件/视频(Agent + Bot) |
93
93
  | AI 自动回复 | ✅ | 接入 OpenClaw Runtime,自动路由 Agent |
94
94
  | Bot 原生 stream 协议 | ✅ | `msgtype=stream` 刷新与增量回包 |
95
+ | Bot 卡片回包 | ✅ | 支持 `markdown/template_card`,失败自动降级文本 |
95
96
  | 多账户 | ✅ | `channels.wecom.accounts.<id>` |
96
97
  | 发送者授权控制 | ✅ | `allowFrom` + 账户级覆盖 |
97
- | 命令白名单 | ✅ | `/help` `/status` `/clear` |
98
+ | 私聊策略(DM) | ✅ | `dm.mode=open/allowlist/deny` + 账户级覆盖 |
99
+ | 事件欢迎语(enter_agent) | ✅ | `events.enterAgentWelcome*` 可配置 |
100
+ | 命令白名单 | ✅ | `/help` `/status` `/clear` `/new` 等 |
98
101
  | 群聊触发策略 | ✅ | 支持 `direct/mention/keyword` 三种模式 |
99
102
  | 文本防抖合并 | ✅ | 窗口期内多条消息合并投递 |
100
103
  | 异步补发(超时后) | ✅ | transcript 轮询补发最终回复 |
104
+ | 观测统计 | ✅ | 入站/回包/错误计数 + 最近失败样本(`/status`) |
101
105
  | WeCom 出站代理 | ✅ | `outboundProxy` / `WECOM_PROXY` |
102
106
 
103
107
  ### 媒体能力
@@ -242,7 +246,7 @@ openclaw gateway restart
242
246
  openclaw gateway status
243
247
  openclaw plugins list
244
248
  npm run wecom:selfcheck -- --all-accounts
245
- npm run wecom:bot:selfcheck
249
+ npm run wecom:bot:selfcheck -- --account default
246
250
  ```
247
251
 
248
252
  `wecom:selfcheck` 帮助:
@@ -269,12 +273,16 @@ node ./scripts/wecom-bot-selfcheck.mjs --help
269
273
  | `agentId` | number/string | - | 应用 AgentId |
270
274
  | `callbackToken` | string | - | 回调 Token(敏感) |
271
275
  | `callbackAesKey` | string | - | 回调 AES Key(敏感) |
272
- | `webhookPath` | string | `/wecom/callback` | Agent 回调路径 |
276
+ | `webhookPath` | string | `/wecom/callback` | Agent 回调路径(非 default 账户未配置时自动生成 `/wecom/<accountId>/callback`) |
277
+ | `agent` | object | - | 兼容旧配置:`agent.corpId/corpSecret/agentId`(与顶层 Agent 字段等价) |
273
278
  | `outboundProxy` | string | - | WeCom 出站代理 |
274
279
  | `webhooks` | object | - | 命名 Webhook 目标映射(如 `{ "ops": "https://...key=xxx" }`) |
275
- | `accounts` | object | - | 多账户配置 |
280
+ | `accounts` | object | - | 多账户配置(支持 `accounts.<id>.bot` 独立 Bot 配置) |
276
281
 
277
- 兼容说明:旧字段 `token` / `encodingAesKey` 仍可用于 Agent 模式(分别等价于 `callbackToken` / `callbackAesKey`),建议逐步迁移到新字段名。
282
+ 兼容说明:支持旧字段与旧结构迁移:`name`、`token` / `encodingAesKey`、`agent.*`、`dynamicAgents.*`、`dm.createAgentOnFirstMessage`、`dm.allowFrom`、`workspaceTemplate`、`commandAllowlist/commandBlockMessage`、`commands.blockMessage`、以及 inline 账户写法 `channels.wecom.<accountId>`。新配置建议优先使用 `accounts.<id>`、`callbackToken/callbackAesKey`、`commands.*` `dynamicAgent.*`。
283
+
284
+ 提示:`accounts.<id>` 现在支持 Bot-only 账号(仅配置 `bot.*`),不再强制要求 `corpId/corpSecret/agentId`。
285
+ 兼容提示:当使用默认新路径时会自动附加 legacy alias,便于旧回调地址平滑迁移:Agent 默认路径会附加 `/webhooks/app`(多账号为 `/webhooks/app/<id>`),Bot 默认路径会附加 `/webhooks/wecom`(多账号为 `/webhooks/wecom/<id>`)。若 alias 与另一类路由冲突会自动跳过并告警。
278
286
 
279
287
  ### Bot 配置(`channels.wecom.bot`)
280
288
 
@@ -283,12 +291,47 @@ node ./scripts/wecom-bot-selfcheck.mjs --help
283
291
  | `enabled` | boolean | `false` | 启用 Bot 模式 |
284
292
  | `token` | string | - | Bot 回调 Token(敏感) |
285
293
  | `encodingAesKey` | string | - | Bot 回调 AESKey(43 位,敏感) |
286
- | `webhookPath` | string | `/wecom/bot/callback` | Bot 回调路径 |
294
+ | `webhookPath` | string | `/wecom/bot/callback` | Bot 回调路径(非 default 账户未配置时自动生成 `/wecom/<accountId>/bot/callback`) |
287
295
  | `placeholderText` | string | `消息已收到...` | stream 初始占位文案(可设为空字符串) |
288
296
  | `streamExpireMs` | integer | `600000` | stream 状态保留时间(30s~1h) |
289
297
  | `replyTimeoutMs` | integer | `90000` | Bot 等待模型回包超时(15s~10m) |
290
298
  | `lateReplyWatchMs` | integer | `180000` | Bot 超时后异步补发观察窗口(30s~10m) |
291
299
  | `lateReplyPollMs` | integer | `2000` | Bot 异步补发轮询间隔(500ms~10s) |
300
+ | `card` | object | 见下方 | Bot 卡片回包策略(`response_url` / `webhook_bot`) |
301
+
302
+ > 重要限制:企业微信官方 Bot 在群聊里通常仅对 `@机器人` 消息触发回调。
303
+ > 因此 Bot 模式下即使配置 `groupChat.triggerMode=direct/keyword`,也会按 `mention` 处理(插件会输出告警)。
304
+
305
+ #### Bot 卡片配置(`channels.wecom.bot.card`)
306
+
307
+ | 键 | 类型 | 默认 | 说明 |
308
+ |---|---|---|---|
309
+ | `enabled` | boolean | `false` | 启用卡片回包 |
310
+ | `mode` | string | `markdown` | `markdown`(兼容优先)或 `template_card` |
311
+ | `title` | string | `OpenClaw-Wechat` | 卡片标题 |
312
+ | `subtitle` | string | - | 卡片副标题 |
313
+ | `footer` | string | - | 卡片底部说明 |
314
+ | `maxContentLength` | integer | `1400` | 卡片正文最大长度(自动截断) |
315
+ | `responseUrlEnabled` | boolean | `true` | 是否在 `response_url` 层发送卡片 |
316
+ | `webhookBotEnabled` | boolean | `true` | 是否在 `webhook_bot` 层发送卡片 |
317
+
318
+ ### 多账户 Bot 覆盖配置(`channels.wecom.accounts.<id>.bot`)
319
+
320
+ 当你启用了 `accounts` 多账户时,可以为每个账户单独配置 Bot 回调密钥、路径、超时和代理;若未配置,则回退到 `channels.wecom.bot`。
321
+
322
+ | 键 | 类型 | 默认 | 说明 |
323
+ |---|---|---|---|
324
+ | `enabled` | boolean | `false` | 是否启用该账户 Bot |
325
+ | `token` / `callbackToken` | string | - | 该账户 Bot 回调 Token(兼容旧字段) |
326
+ | `encodingAesKey` / `callbackAesKey` | string | - | 该账户 Bot 回调 AESKey(兼容旧字段) |
327
+ | `webhookPath` | string | `/wecom/bot/callback` | 该账户 Bot 回调路径 |
328
+ | `placeholderText` | string | `消息已收到...` | stream 初始占位文案 |
329
+ | `streamExpireMs` | integer | `600000` | stream 状态保留时间 |
330
+ | `replyTimeoutMs` | integer | `90000` | Bot 等待模型回包超时 |
331
+ | `lateReplyWatchMs` | integer | `180000` | 超时后异步补发观察窗口 |
332
+ | `lateReplyPollMs` | integer | `2000` | 异步补发轮询间隔 |
333
+ | `card` | object | - | 该账户专用卡片回包配置(覆盖全局 `bot.card`) |
334
+ | `outboundProxy` / `proxyUrl` / `proxy` | string | - | 该账户 Bot 专用代理(优先于全局) |
292
335
 
293
336
  ### 授权与指令策略
294
337
 
@@ -298,8 +341,10 @@ node ./scripts/wecom-bot-selfcheck.mjs --help
298
341
  | 拒绝文案 | `allowFromRejectMessage` | 未授权提示 |
299
342
  | 管理员 | `adminUsers` | 绕过命令白名单 |
300
343
  | 命令白名单 | `commands.enabled` + `commands.allowlist` | 限制 `/` 指令 |
301
- | 群聊触发 | `groupChat.enabled` + `triggerMode` + `mentionPatterns` + `triggerKeywords` | 控制群消息触发条件 |
302
- | 动态路由 | `dynamicAgent.*`(含 `workspaceTemplate`) | 动态 Agent + workspace bootstrap 播种 |
344
+ | 私聊策略 | `dm.mode` + `dm.allowFrom` + `dm.rejectMessage` | 控制私聊开放/白名单/拒绝 |
345
+ | 事件策略 | `events.enabled` + `events.enterAgentWelcomeEnabled` + `events.enterAgentWelcomeText` | 控制事件处理与 enter_agent 欢迎语 |
346
+ | 群聊触发 | `groupChat.enabled` + `triggerMode` + `mentionPatterns` + `triggerKeywords` | 控制群消息触发条件(自建应用支持 `direct/mention/keyword`;Bot 模式按平台限制固定 `mention`) |
347
+ | 动态路由 | `dynamicAgent.*`(兼容 `dynamicAgents.*`、`dm.createAgentOnFirstMessage`) | 动态 Agent + workspace bootstrap 播种 |
303
348
 
304
349
  ### 吞吐与稳定性
305
350
 
@@ -308,6 +353,7 @@ node ./scripts/wecom-bot-selfcheck.mjs --help
308
353
  | 文本防抖 | `debounce.enabled/windowMs/maxBatch` | 合并短时间多条文本 |
309
354
  | Agent 增量回包 | `streaming.enabled/minChars/minIntervalMs` | 多消息模拟流式 |
310
355
  | 异步补发 | `WECOM_LATE_REPLY_WATCH_MS/POLL_MS` | dispatch 超时后补发最终回复 |
356
+ | 观测统计 | `observability.enabled/logPayloadMeta` | 记录入站/回包/错误并在 `/status` 展示 |
311
357
 
312
358
  ### 语音转写(本地)
313
359
 
@@ -350,6 +396,14 @@ node ./scripts/wecom-bot-selfcheck.mjs --help
350
396
  "callbackAesKey": "aes-sales",
351
397
  "webhookPath": "/wecom/sales/callback",
352
398
  "outboundProxy": "http://10.0.0.5:8888",
399
+ "bot": {
400
+ "enabled": true,
401
+ "token": "sales-bot-token",
402
+ "encodingAesKey": "sales-bot-aes",
403
+ "webhookPath": "/wecom/sales/bot/callback",
404
+ "replyTimeoutMs": 120000,
405
+ "outboundProxy": "http://10.0.0.9:7890"
406
+ },
353
407
  "allowFrom": ["alice", "wecom:bob"],
354
408
  "allowFromRejectMessage": "销售助手未授权,请联系管理员。"
355
409
  }
@@ -392,8 +446,8 @@ node ./scripts/wecom-bot-selfcheck.mjs --help
392
446
  | `/help` | 查看帮助 |
393
447
  | `/status` | 查看运行状态 |
394
448
  | `/clear` | 清理会话(兼容映射到 `/reset`) |
449
+ | `/new` | 新建会话(兼容映射到 `/reset`) |
395
450
  | `/reset` | 重置会话 |
396
- | `/new` | 新建会话(由上层运行时支持) |
397
451
  | `/compact` | 压缩会话(由上层运行时支持) |
398
452
 
399
453
  ### 会话策略
@@ -437,6 +491,16 @@ node ./scripts/wecom-bot-selfcheck.mjs --help
437
491
  | `WECOM_BOT_REPLY_TIMEOUT_MS` | 否 | Bot 等待模型回包超时 |
438
492
  | `WECOM_BOT_LATE_REPLY_WATCH_MS` | 否 | Bot 超时后补发观察窗口 |
439
493
  | `WECOM_BOT_LATE_REPLY_POLL_MS` | 否 | Bot 补发轮询间隔 |
494
+ | `WECOM_BOT_CARD_ENABLED` | 否 | 是否启用 Bot 卡片回包 |
495
+ | `WECOM_BOT_CARD_MODE` | 否 | 卡片模式:`markdown` / `template_card` |
496
+ | `WECOM_BOT_CARD_TITLE` | 否 | 卡片标题 |
497
+ | `WECOM_BOT_CARD_SUBTITLE` | 否 | 卡片副标题 |
498
+ | `WECOM_BOT_CARD_FOOTER` | 否 | 卡片底部说明 |
499
+ | `WECOM_BOT_CARD_MAX_CONTENT_LENGTH` | 否 | 卡片正文最大长度 |
500
+ | `WECOM_BOT_CARD_RESPONSE_URL_ENABLED` | 否 | response_url 层卡片开关 |
501
+ | `WECOM_BOT_CARD_WEBHOOK_BOT_ENABLED` | 否 | webhook_bot 层卡片开关 |
502
+ | `WECOM_<ACCOUNT>_BOT_*` | 否 | 账户级 Bot 覆盖(如 `WECOM_SALES_BOT_TOKEN`) |
503
+ | `WECOM_<ACCOUNT>_BOT_PROXY` | 否 | 账户级 Bot 媒体下载/回包代理 |
440
504
 
441
505
  ### 策略与流控
442
506
 
@@ -446,10 +510,13 @@ node ./scripts/wecom-bot-selfcheck.mjs --help
446
510
  | `WECOM_ALLOW_FROM_REJECT_MESSAGE` / `WECOM_<ACCOUNT>_ALLOW_FROM_REJECT_MESSAGE` | 未授权提示 |
447
511
  | `WECOM_ADMIN_USERS` | 管理员用户列表 |
448
512
  | `WECOM_COMMANDS_ENABLED` / `WECOM_COMMANDS_ALLOWLIST` / `WECOM_COMMANDS_REJECT_MESSAGE` | 命令白名单策略 |
513
+ | `WECOM_DM_POLICY` / `WECOM_DM_MODE` / `WECOM_DM_ALLOW_FROM` / `WECOM_DM_REJECT_MESSAGE` | 私聊策略(支持 `WECOM_<ACCOUNT>_DM_*` 覆盖) |
514
+ | `WECOM_EVENTS_ENABLED` / `WECOM_EVENTS_ENTER_AGENT_WELCOME_ENABLED` / `WECOM_EVENTS_ENTER_AGENT_WELCOME_TEXT` | 事件处理与 enter_agent 欢迎语(支持 `WECOM_<ACCOUNT>_EVENTS_*`) |
449
515
  | `WECOM_GROUP_CHAT_ENABLED` / `WECOM_GROUP_CHAT_REQUIRE_MENTION` / `WECOM_GROUP_CHAT_MENTION_PATTERNS` | 群触发策略 |
450
516
  | `WECOM_DEBOUNCE_ENABLED` / `WECOM_DEBOUNCE_WINDOW_MS` / `WECOM_DEBOUNCE_MAX_BATCH` | 文本防抖 |
451
517
  | `WECOM_STREAMING_ENABLED` / `WECOM_STREAMING_MIN_CHARS` / `WECOM_STREAMING_MIN_INTERVAL_MS` | Agent 增量回包 |
452
518
  | `WECOM_LATE_REPLY_WATCH_MS` / `WECOM_LATE_REPLY_POLL_MS` | 异步补发窗口与轮询频率 |
519
+ | `WECOM_OBSERVABILITY_ENABLED` / `WECOM_OBSERVABILITY_PAYLOAD_META` | 观测统计与载荷元信息日志开关 |
453
520
 
454
521
  ### 语音回退转写
455
522
 
@@ -486,6 +553,7 @@ node ./scripts/wecom-bot-selfcheck.mjs --help
486
553
  | 能收到消息但不回复 | `openclaw gateway status` + `openclaw logs --follow` | 模型超时、会话排队、权限策略拦截 | 查看 dispatch/allowFrom/commands 日志 |
487
554
  | Bot 图片识别失败 | `wecom(bot): failed to fetch image url` | URL 失效、返回非图像流 | 已支持 octet-stream+解密兜底,先升级到最新版本 |
488
555
  | 语音转写失败 | `wecom: voice transcription failed` | 本地命令或模型路径错误 | 检查 `command`、`modelPath`、`ffmpeg` |
556
+ | 启动出现账号体检告警 | `wecom: account diagnosis ...` | 多账号 Token/Agent/路径存在冲突风险 | 按日志 `code` 与账户列表调整配置,优先处理 `warn` 级别 |
489
557
  | gettoken 失败 | 企业微信 API 返回码 | CorpId/Secret 错或网络受限 | 检查凭据/配置代理 |
490
558
 
491
559
  ### 推荐检查命令
@@ -496,7 +564,7 @@ openclaw status --deep
496
564
  openclaw logs --follow
497
565
  npm run wecom:selfcheck -- --all-accounts
498
566
  npm run wecom:agent:selfcheck -- --account default
499
- npm run wecom:bot:selfcheck
567
+ npm run wecom:bot:selfcheck -- --all-accounts
500
568
  ```
501
569
 
502
570
  ## 开发与发布
@@ -506,12 +574,22 @@ npm run wecom:bot:selfcheck
506
574
  | 命令 | 作用 |
507
575
  |---|---|
508
576
  | `npm test` | 语法与单测 |
509
- | `WECOM_E2E_ENABLE=1 npm run test:e2e:remote` | 运行远程 E2E 测试(默认跳过,需配 `WECOM_E2E_BOT_URL`/`WECOM_E2E_AGENT_URL`) |
577
+ | `WECOM_E2E_ENABLE=1 npm run test:e2e:remote` | 运行远程 E2E 测试(默认跳过;支持 `WECOM_E2E_*` 与兼容 `E2E_WECOM_*` 两套变量) |
578
+ | `WECOM_E2E_MATRIX_ENABLE=1 npm run test:e2e:matrix` | 运行远程矩阵 E2E(签名验签/异常请求/stream refresh/去重链路) |
579
+ | `npm run test:e2e:prepare-browser` | 远程浏览器沙箱就绪检查(可选自动安装 Chromium) |
580
+ | `npm run test:e2e:collect-pdf` | 收集远端浏览器沙箱中的 PDF 产物到本地 |
510
581
  | `npm run wecom:selfcheck -- --all-accounts` | 配置+网络体检 |
511
582
  | `npm run wecom:agent:selfcheck -- --account <id>` | Agent 端到端链路体检(URL 验证 + 加密 POST) |
512
- | `npm run wecom:bot:selfcheck` | Bot 端到端链路体检(签名/加密/stream-refresh |
583
+ | `npm run wecom:bot:selfcheck -- --account <id>` | Bot 端到端链路体检(URL 验证/签名/加密/stream-refresh,支持多账户) |
513
584
  | `npm run wecom:remote:e2e -- --mode all --agent-url <公网Agent回调> --bot-url <公网Bot回调>` | 远端矩阵验证(Agent+Bot) |
514
- | `GitHub Actions -> CI -> Run workflow (run_remote_e2e=true)` | 在仓库 CI 手动触发远程 E2E(使用 `WECOM_E2E_*` secrets) |
585
+ | `npm run wecom:remote:e2e -- --mode all --agent-url <公网Agent回调> --bot-url <公网Bot回调> --prepare-browser --collect-pdf` | 远端矩阵验证(含浏览器沙箱检查与 PDF 回收) |
586
+ | `WECOM_E2E_BOT_URL=<...> WECOM_E2E_AGENT_URL=<...> npm run wecom:remote:e2e -- --mode all` | 用环境变量驱动远端 E2E(兼容旧 `E2E_WECOM_*`) |
587
+ | `npm run wecom:e2e:scenario -- --scenario full-smoke --agent-url <公网Agent回调> --bot-url <公网Bot回调>` | 场景化 E2E(预置 smoke/queue 场景) |
588
+ | `npm run wecom:e2e:scenario -- --scenario compat-smoke --agent-url <新Agent回调> --agent-legacy-url <旧Agent回调> --bot-url <新Bot回调> --bot-legacy-url <旧Bot回调>` | 兼容矩阵验证(新旧回调地址都跑一遍) |
589
+ | `npm run wecom:e2e:scenario -- --scenario matrix-smoke --bot-url <公网Bot回调>` | Bot 协议矩阵验证(验签/异常请求/stream-refresh/去重;需 `WECOM_BOT_TOKEN/WECOM_BOT_ENCODING_AES_KEY`) |
590
+ | `npm run wecom:e2e:compat -- --agent-url <新Agent回调> --agent-legacy-url <旧Agent回调> --bot-url <新Bot回调> --bot-legacy-url <旧Bot回调>` | 兼容矩阵快捷命令(等价 `--scenario compat-smoke`) |
591
+ | `npm run wecom:e2e:full -- --agent-url <公网Agent回调> --bot-url <公网Bot回调>` | 一键 full-smoke(默认带 `--prepare-browser --collect-pdf`) |
592
+ | `GitHub Actions -> CI -> Run workflow` | 在仓库 CI 手动触发远程 E2E:设置 `run_remote_e2e=true`,可选 `e2e_scenario`(含 `compat-smoke`)与浏览器参数 |
515
593
  | `npm run wecom:smoke` | 升级后快速回归(Agent 主链路) |
516
594
  | `npm run wecom:smoke -- --with-bot-e2e` | 升级后快速回归(含 Bot E2E) |
517
595
  | `openclaw gateway restart` | 重启网关 |
@@ -537,6 +615,16 @@ npm run wecom:bot:selfcheck
537
615
  ### Q4:支持个人微信吗?
538
616
  支持企业微信场景下的“微信插件入口”(个人微信扫码进入企业应用对话),不等同于“个人微信网页版协议”。
539
617
 
618
+ ### Q4.1:为什么 Bot 模式在“微信插件入口”里看不到机器人联系人?
619
+ 这是企业微信产品形态差异,不是插件故障。
620
+ 在很多租户里,“微信插件入口”主要对应**自建应用(Agent 回调)**可见,Bot 模式(智能机器人 API)不会以可直接会话的联系人形态出现。
621
+
622
+ 建议:
623
+ 1. 需要稳定私聊入口:优先用自建应用(Agent 模式)
624
+ 2. 需要群聊通知/群内对话:优先用 Webhook Bot / Bot 模式
625
+ 3. 需要两者兼顾:并行启用 Agent(入口)+ Bot(群能力)
626
+ 4. 运行 `npm run wecom:bot:selfcheck -- --account <id>`(或 `--all-accounts`)可看到 `bot.entry.visibility` 提示,用于快速确认该行为属于产品形态差异而非插件故障
627
+
540
628
  ### Q5:为什么 `curl https://域名/wecom/callback` 返回的是 WebUI 页面?
541
629
  这是路由层问题,不是插件正常行为。`GET /wecom/callback`(无 `echostr`)应返回纯文本 `wecom webhook ok`。
542
630
  若返回 WebUI,说明你的反向代理把该路径转发到了前端服务而不是 OpenClaw 网关。
@@ -548,10 +636,10 @@ npm run wecom:bot:selfcheck
548
636
 
549
637
  ### Q6:自建应用群聊怎么开?为什么群里不 @ 就不触发?
550
638
  先区分两种通道能力:
551
- 1. **群机器人(Webhook Bot)**:可直接添加到企微群,天然适合群聊收发。
639
+ 1. **群机器人(Webhook Bot)**:可直接添加到企微群,天然适合群聊收发;但群聊通常仅 `@机器人` 时才会回调。
552
640
  2. **自建应用(Agent 回调)**:插件支持处理 `ChatId` 群消息;但是否能收到“普通群消息”取决于企业微信实际下发能力(很多租户里普通群只能加机器人,无法像成员一样加自建应用)。
553
641
 
554
- 如果你的场景是“普通群里直接聊天并稳定触发”,优先用 **Webhook Bot 模式**。
642
+ 如果你的场景是“普通群里稳定对话”,优先用 **Webhook Bot 模式**(注意通常仍需 `@机器人` 才触发)。
555
643
  如果你确认企业微信会把群消息回调到自建应用(日志里有 `chatId=...`),再配置触发模式:
556
644
 
557
645
  ```json