omp-wechat 1.2.1 → 1.3.0

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/README.md CHANGED
@@ -31,6 +31,7 @@ For boot-time persistence, install a launchd/systemd service via `/wechat instal
31
31
  - **Singleton**: port lock guarantees one poll loop across all concurrent OMP/Pi processes — no duplicate replies
32
32
  - **Failover**: 30s timer takes over automatically if the lock holder crashes
33
33
  - **Bidirectional**: receive and reply to WeChat text messages
34
+ - **Image recognition**: inbound images are downloaded from WeChat CDN, AES-decrypted, and passed to the vision model
34
35
  - **Per-chat sessions**: each WeChat chat gets an independent AI session (concurrent, isolated)
35
36
  - **LRU pool**: caps memory usage by evicting least-recently-used sessions (default: 50)
36
37
  - **Typing indicator**: native WeChat "Typing..." shown during AI processing
@@ -108,6 +109,8 @@ systemPrompt: |
108
109
  | `systemPrompt` | Built-in | System prompt for WeChat chat sessions |
109
110
 
110
111
  > **Model and tools are managed by OMP/Pi.** `createAgentSession()` automatically calls `discoverAuthStorage()`, reusing your existing `omp login` / `pi login` OAuth, `~/.omp/agent/agent.db` API keys, or `models.yml` config. This project never touches API keys.
112
+ >
113
+ > **Image recognition** requires a vision model role configured in OMP (e.g. `omp model role vision xfyun/xopkimik25`). If no vision role is set, inbound images are skipped — only the text placeholder is sent to the AI.
111
114
 
112
115
  ## Slash Commands
113
116
 
@@ -188,11 +191,12 @@ OMP-Wechat/
188
191
  - **Reply-only**: iLink requires `context_token` from an inbound message; you cannot initiate conversations
189
192
  - **1:1 only**: iLink Bot API does not support group chats
190
193
  - **Single instance**: iLink allows only one bot connection per account
191
- - **Text only (Phase 1)**: images/voice/video are represented as `(image)` / `(voice)` placeholders; media support is planned
194
+ - **Media**: inbound images are downloaded from WeChat CDN, AES-decrypted, and passed to the vision model (if `modelRoles.vision` is configured); voice/video remain as placeholders
192
195
 
193
196
  ## Roadmap
194
197
 
195
- - [ ] **Phase 2**: Media support (inbound images as base64, voice transcription)
198
+ - [x] **Phase 2a**: Inbound image support (CDN download + AES decrypt + vision model)
199
+ - [ ] **Phase 2b**: Voice transcription / video support
196
200
  - [x] **Phase 3**: Persistent sessions — `SessionManager.continueRecent()` per chat, context survives restarts
197
201
  - [x] **Phase 4**: Per-chat model selection — `/model` `/models` chat commands for manual switching
198
202
  - [ ] **Phase 5**: Fine-grained permissions (per-user tool restrictions, bash approval via WeChat)
package/dist/index.js CHANGED
@@ -1758,17 +1758,15 @@ function decryptAesEcb(ciphertext, key) {
1758
1758
  const decipher = createDecipheriv("aes-128-ecb", key, null);
1759
1759
  return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
1760
1760
  }
1761
- function buildCdnUrl(fileUrl, fullUrl) {
1761
+ function buildCdnUrl(encryptQueryParam, fullUrl) {
1762
1762
  if (fullUrl)
1763
1763
  return fullUrl;
1764
- if (!fileUrl)
1764
+ if (!encryptQueryParam)
1765
1765
  return null;
1766
- if (fileUrl.startsWith("http"))
1767
- return fileUrl;
1768
- return `${CDN_BASE_URL}/download?encrypted_query_param=${encodeURIComponent(fileUrl)}`;
1766
+ return `${CDN_BASE_URL}/download?encrypted_query_param=${encodeURIComponent(encryptQueryParam)}`;
1769
1767
  }
1770
- async function downloadAndDecrypt(fileUrl, fullUrl, aeskeyHex, aesKeyBase64, label = "media") {
1771
- const url = buildCdnUrl(fileUrl, fullUrl);
1768
+ async function downloadAndDecrypt(encryptQueryParam, fullUrl, aeskeyHex, aesKeyBase64, label = "media") {
1769
+ const url = buildCdnUrl(encryptQueryParam, fullUrl);
1772
1770
  if (!url) {
1773
1771
  logger.warn(`[${label}] No CDN URL available`);
1774
1772
  return null;
@@ -2411,7 +2409,8 @@ class WeChatBridge {
2411
2409
  const results = [];
2412
2410
  for (const item of imageItems) {
2413
2411
  const img = item.image_item;
2414
- const buf = await downloadAndDecrypt(img.file_url, img.full_url, img.aeskey, img.aes_key ?? img.media?.aes_key, `image[${chatId}]`);
2412
+ logger.debug(`[${chatId}] image_item raw: ${JSON.stringify(img)}`);
2413
+ const buf = await downloadAndDecrypt(img.media?.encrypt_query_param, img.media?.full_url, img.aeskey, img.media?.aes_key, `image[${chatId}]`);
2415
2414
  if (buf) {
2416
2415
  const mimeType = buf.length > 4 && buf[0] === 137 && buf[1] === 80 ? "image/png" : "image/jpeg";
2417
2416
  results.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omp-wechat",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "OMP/Pi extension: bridge WeChat messages to OMP's AI engine via the iLink Bot API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",