npm package: @gloablehive/ipad-wechat-plugin
Description: OpenClaw channel plugin for iPad WeChat protocol - enables sending/receiving WeChat messages through JuHeBot API
Latest version: 2.6.4

# @gloablehive/ipad-wechat-plugin

OpenClaw channel plugin for iPad WeChat protocol — enables sending and receiving WeChat messages through the JuHeBot API.

## Features

- **Inbound**: Receive WeChat messages via JuHeBot webhook callbacks
- **Outbound**: Auto-reply via agent → JuHeBot `send_text` API
- **FTS5 Knowledge Injection**: Before sending to agent, search knowledge base via SQLite FTS5 and inject relevant chunks into the message — agent answers directly without reading files
- **Multi-account**: Support multiple WeChat accounts via config
- **Deduplication**: Prevents duplicate message processing (msg_id TTL)
- **Chatroom support**: Direct messages and group chats
- **Message caching**: Integrated with `@gloablehive/cache`

## Architecture

```
WeChat User
    ↓ (message)
JuHeBot Cloud (notify_type=1010)
    ↓ (HTTP POST callback)
Cloudflare Tunnel (ipad-wechat.jigongopc.com)
    ↓
Standalone HTTP Server (:18790)
    ↓ transformPayload() + dedup
FTS5 Knowledge Search (extractKeywords → SQLite LIKE/BM25)
    ↓ inject matching chunks into message
Gateway WebSocket (chat.send → chat.history polling)
    ↓
OpenClaw Agent (answers using injected knowledge)
    ↓ (agent reply detected via polling)
JuHeBot API (/msg/send_text, to_username)
    ↓
WeChat User (receives reply)
```

## Setup

### 1. Install

```bash
npm install @gloablehive/ipad-wechat-plugin
```

### 2. Configure OpenClaw

Add to your `openclaw.json` under `plugins.entries`:

```json
{
  "ipad-wechat": {
    "config": {
      "appKey": "<your-juhebot-app-key>",
      "appSecret": "<your-juhebot-app-secret>",
      "guid": "<your-instance-guid>"
    }
  }
}
```

### 3. Set JuHeBot Notify URL

After the plugin starts (webhook server on port 18790), set the callback URL:

```bash
curl -X POST "https://chat-api.juhebot.com/open/GuidRequest" \
  -H "Content-Type: application/json" \
  -d '{
    "app_key": "<appKey>",
    "app_secret": "<appSecret>",
    "path": "/client/set_notify_url",
    "data": {
      "guid": "<guid>",
      "notify_url": "https://<your-public-url>/"
    }
  }'
```

> Use a Cloudflare tunnel (`cloudflared tunnel --url http://127.0.0.1:18790`) if you don't have a public IP.

### 4. Environment Variables

| Variable | Default | Description |
|---|---|---|
| `IPAD_WECHAT_WEBHOOK_PORT` | `18790` | Standalone webhook server port |
| `OPENCLAW_GATEWAY_PORT` | `18789` | OpenClaw gateway WS port |

## JuHeBot API Notes

All JuHeBot API parameters use **snake_case**:

| Method | Path | Key Params |
|---|---|---|
| Send text | `/msg/send_text` | `to_username`, `content` |
| Send room @ | `/msg/send_room_at` | `to_username`, `content`, `at_list` |
| Set notify URL | `/client/set_notify_url` | `notify_url` |

### Callback Format (notify_type=1010)

```json
{
  "guid": "...",
  "notify_type": 1010,
  "data": {
    "from_username": "wxid_xxx",
    "to_username": "wxid_yyy",
    "desc": "NickName : message content",
    "msg_id": "123456",
    "msg_type": 1,
    "is_chatroom_msg": 0,
    "chatroom": ""
  }
}
```

> Note: There is **no `content` field** — text is extracted from `desc` by stripping the `"NickName : "` prefix.

## Service Management (macOS launchd)

Both core services run as `launchd` daemons with `KeepAlive=true` — they auto-restart on crash and auto-start on boot.

| Service | LaunchAgent | Listens | Logs |
|---------|-------------|---------|------|
| OpenClaw Gateway | `ai.openclaw.gateway.plist` | `:18789` (WS) | `~/.openclaw/logs/gateway.log` |
| Cloudflare Tunnel | `com.cloudflare.tunnel.ipad-wechat.plist` | `ipad-wechat.jigongopc.com` → `:18790` | `~/.openclaw/logs/cloudflared.err.log` |

### Commands

```bash
# Check status
launchctl list | grep -E "openclaw|cloudflare"

# Restart gateway
openclaw gateway restart

# Restart cloudflared
launchctl unload ~/Library/LaunchAgents/com.cloudflare.tunnel.ipad-wechat.plist
launchctl load ~/Library/LaunchAgents/com.cloudflare.tunnel.ipad-wechat.plist

# View logs
tail -f ~/.openclaw/logs/gateway.log
tail -f ~/.openclaw/logs/cloudflared.err.log
```

### Troubleshooting

If messages stop coming through:

1. **Check Cloudflare tunnel**: `pgrep -fl cloudflared` — if not running, load the LaunchAgent
2. **Check webhook server**: `curl -s http://localhost:18790/` — should return `200`
3. **Check JuHeBot notify URL**: Must be `https://ipad-wechat.jigongopc.com/`
4. **Check gateway**: `launchctl list ai.openclaw.gateway` — exit status should be `0`

## Version History

### 2.4.0

- **FTS5 Knowledge Injection**: Before `chat.send`, search `~/.openclaw/knowledge/` via SQLite FTS5 and inject relevant chunks into the message. Agent answers directly using injected knowledge without calling `read` tools.
- **extractKeywords()**: Chinese NLP keyword extraction — splits natural queries into search terms (e.g. `"VIP有什么优惠？"` → `["VIP", "优惠"]`), enabling accurate LIKE fallback for CJK text.
- **CacheManager absolute basePath**: Fixed `ENOENT ./cache` error — cache directory now resolves to `~/.openclaw/channels/ipad-wechat/cache`.
- **await CacheManagerReady**: Ensures knowledge indexing completes before searching.
- **Cloudflare tunnel LaunchAgent**: `com.cloudflare.tunnel.ipad-wechat.plist` — auto-start on boot, auto-restart on crash via `launchd`.

### 2.1.0

- **Outbound message caching**: Agent replies are written to `@gloablehive/cache` after successful JuHeBot send (direction: `outbound`, isSelf: `true`)
- **WS connection pool**: Reuse gateway WebSocket connections instead of opening a new one per RPC call. Pool of up to 3 connections with 30s idle timeout. Reduces connection overhead during `chat.history` polling from ~42 to ~3 connections per message.
- **Cloudflare named tunnel script**: `scripts/setup-cloudflare-tunnel.sh` for permanent tunnel URL that survives restarts. Auto-updates JuHeBot `notifyUrl` on start.

### 2.0.0

- Full end-to-end inbound + outbound message flow
- Real JuHeBot callback format support (`desc` field parsing)
- Fixed all API params to snake_case (`to_username`, `notify_url`)
- Message deduplication by `msg_id`
- Chatroom field mapping (`is_chatroom_msg` + `chatroom`)
- Gateway WS polling strategy for agent reply detection
- Short-lived WS connections per RPC call

### 1.x

- Initial implementation with webhook receiver
- Cache integration
- Multi-account scaffolding

## License

MIT