# OpenClaw WeCom 插件 v2.3.12 变更简报

> [!TIP]
> **WS 主动推送与过期更新兜底版本**：`v2.3.12` 重点修复企业微信 Bot WebSocket 在流式回复超过 6 分钟后返回 `846608 stream message update expired`，并同步调整主动消息路由为“WS 文本优先、Agent 文件兜底”。

## 2026-03-12（v2.3.12）
- 【6 分钟过期修复】🛠 **[重要修复]** `Bot WS` 现在会把企业微信 `846608 stream message update expired (>6 minutes)` 识别为回复窗口已结束的终态错误，不再继续把该错误向上抛出导致进程退出。
- 【主动推送路由收敛】🚀 **[重要修复]** 当账号实际运行在 `Bot WS` 模式时，定时消息、heartbeat 和其他主动文本发送现在优先走 `wsClient.sendMessage()`，不再默认绕去 Agent。
- 【WS/Agent 职责切分】🧩 `Agent` 现在主要承担两类兜底：一是账号没有启用 `Bot WS` 时的主动消息发送；二是 Bot 两种模式都不支持的文件/媒体发送。
- 【UserID 纯数字解析修复】🛠 **[重要修复]** 解决 Agent 模式下纯数字 UserID 被误判为部门 ID 导致的图片发送失败（81013 错误）。在 `wecom-agent:` 作用域下，纯数字目标现在优先解析为用户。
- 【未处理拒绝隔离】🧯 `sdk-adapter` 为每个 WebSocket frame 的异步处理补上显式兜底捕获；即使后续再出现漏网异常，也会记录到 runtime issue，而不是变成 `unhandledRejection` 直接带崩 OpenClaw。
- 【占位保活收敛】⏱ 当回复窗口已经过期时，WS 占位符保活会立即停止，避免过期后继续发送流式更新。
- 【Ack 超时兜底】⏱ SDK 5 秒回执超时 (`Reply ack timeout`) 现在也被识别为终态错误，超时后立即停止占位保活并走 `onFail` 回调，不再产生 `unhandledRejection`。
- 【回归测试补齐】✅ 新增针对 `846608` 过期更新和 `frame handler` reject 的回归测试，确保此类异常保持非致命。
- 【Bot WS 图片/文件解密修复】🛠 **[重要修复]** `Bot WS` 模式下接收到的图片和文件现在会使用消息体中的独立 `aeskey` 进行 AES-256-CBC 解密，修复之前直接保存密文导致 `Failed to optimize image` 的问题。`media-service.ts` 新增 `downloadEncryptedMedia()` 方法，`normalizeFirstAttachment()` 自动检测 `aesKey` 并走解密路径。

## 验证结果
- `pnpm exec vitest -c extensions/wecom/vitest.config.ts extensions/wecom/src/transport/bot-ws/reply.test.ts extensions/wecom/src/transport/bot-ws/sdk-adapter.test.ts`
- `pnpm exec vitest -c extensions/wecom/vitest.config.ts extensions/wecom/src/outbound.test.ts extensions/wecom/src/transport/bot-ws/reply.test.ts extensions/wecom/src/transport/bot-ws/sdk-adapter.test.ts`
- `pnpm exec tsc -p extensions/wecom/tsconfig.json --noEmit`

## 升级提示
- 无需新增配置，升级到 `v2.3.12` 后自动生效。
- 如果账号正在使用 `Bot WS`，主动文本消息会优先走 WS 长连接；只有文件/媒体或未启用 WS 的场景才会继续使用 Agent。
- 如果模型或工具链路存在超长执行，超过企业微信 6 分钟回复窗口时，OpenClaw 现在会保持存活并记录运行时错误，不再因该异常退出。
