# @ericzhaozzz/wechat-channel

**English** | [中文](README.zh-CN.md)

> A pi agent extension that turns your personal WeChat into a remote control for your
> AI assistant — chat with pi from your phone and get answers, progress and files back
> in WeChat, via Tencent's iLink Bot API.

## Features

- **Chat from WeChat, get one coherent answer** — messages are forwarded to pi's agent
  and replies keep their Markdown. A burst of short messages is merged into a single
  numbered turn (`[1]`, `[2]`, …), and long replies arrive chunked and in order.
- **Watch the work happen** — the typing indicator stays on for the whole turn and
  progress lines (`⏳ 正在执行：npm test`) show what the agent is doing; queueing,
  offline replay and undelivered answers are all called out.
- **Rich media, both ways** — images, files, voice and video are auto-downloaded, and
  received images enter the model's context as image content. The agent can push
  screenshots, charts and generated files before the answer is finished.
- **Control it from your phone** — `#status`, `#queue`, `#clear`, `#cancel` and
  `#help` work straight from WeChat.
- **The agent asks you, not the empty terminal** — a tool that can only put its question
  to a terminal dialog (`ask_user_question`) would block the whole turn on a screen nobody
  is watching. On a WeChat turn those tools are blocked and the agent is told to ask as
  ordinary numbered text, so the question reaches your phone and "2" is a complete answer.
  Configurable through `terminalOnlyTools`.
- **One person, one session** — exactly one WeChat user is bound as owner and everyone
  else is dropped silently; the channel belongs to a single pi session.
- **Keeps working through trouble** — auto-reconnect and re-login, offline traffic is
  replayed, messages queue instead of dropping, and state survives restarts and crashes.

## Installation

```bash
# From npm (preferred) — ships the compiled dist/, nothing else needed
pi install npm:@ericzhaozzz/wechat-channel
pi

# Or build from source and copy into pi's extension folder — the compiled
# dist/ is what pi loads (`pi.extensions` points at `./dist/index.js`)
npm install && npm run build          # inside the wechat-channel checkout
cd .. && cp -r wechat-channel ~/.pi/agent/extensions/
cd ~/.pi/agent/extensions/wechat-channel
npm install --omit=dev                # just the SDK — no tsx at runtime

# Restart pi (or run /reload)
pi
```

## Quick start

1. Start pi and run `/wechat login`
2. Scan the QR code with WeChat on your phone and confirm the login
3. **Send the bot a message from your own WeChat** — the first sender is bound as owner
4. Your messages are forwarded to pi; replies come back to you automatically

## Usage

### Commands from WeChat

Away from the terminal, the owner can send these straight to the bot. The prefix is
`channelCommandPrefix` (default `#`; an empty string disables them):

| Command | Effect |
|---------|--------|
| `#cancel` / `#停` | Abort the turn being answered |
| `#status` / `#状态` | Connection, busy or idle, queue depth, context usage |
| `#queue` / `#队列` | How many messages are waiting |
| `#clear` / `#清空` | Drop the queue (the running turn is untouched) |
| `#help` / `#帮助` | List the above |

Only an **exact** command name counts — a typo like `#cancle` is forwarded to the agent
like any other message.

### Terminal commands

`/wechat login`, `/wechat status`, `/wechat config`, `/wechat history`, `/wechat log
--tail`, `/wechat owner`, `/wechat send` / `send-media`, `/wechat start` / `stop`,
`/wechat qr`, `/wechat cancel`, `/wechat clean` and `/wechat prefix` cover login, status,
ownership, sending and media (subcommands autocomplete — type `/wechat st` and press Tab).
pi's footer carries one line of channel state (`wechat: ✓ busy q2`); disconnects, dropped
answers and the like surface as pi notifications.

## Configuration

Everything tunable lives in `~/.pi/wechat/config.json` and is read once at load, so
changes apply after `/reload`. Without the file, the extension behaves exactly as it
ships:

```json
{
  "ownerUserId": "wxid_xxx@im.wechat",
  "aiPrefix": "",
  "autoLogin": true,
  "preserveMarkdown": true,
  "mergeDebounceMs": 1500,
  "maxMergeMessages": 10,
  "rateLimitMax": 20,
  "rateLimitWindowMs": 60000,
  "agentTimeoutMs": 3600000,
  "maxDeliveryAttempts": 3,
  "deliveryParkTimeoutMs": 600000,
  "progressUpdates": true,
  "progressIntervalMs": 45000,
  "notifyFailures": true,
  "channelCommandPrefix": "#",
  "inlineImages": true,
  "maxInlineImageBytes": 5242880,
  "terminalOnlyTools": ["ask_user_question"],
  "steerWhileBusy": false
}
```

The commonly tuned ones: `channelCommandPrefix` (WeChat command prefix), `ownerUserId`
(pin the owner up front), `mergeDebounceMs` / `maxMergeMessages` (short-message merging),
`steerWhileBusy` (a message arriving mid-turn joins that turn instead of queueing),
`inlineImages`, `progressUpdates` and `notifyFailures`. `terminalOnlyTools` names the tools
that may not run on a WeChat turn because they can only ask through a terminal dialog —
empty it if you do sit at the terminal, and such a question is relayed to WeChat as a notice
telling you to answer it there. Unreadable values or unknown keys keep their defaults and
are listed by `/wechat config`.

## Security & ownership

Anyone who could reach the bot could otherwise read your files and run commands through
pi, so messages from anyone but the owner are **dropped silently**. The owner is bound on
first use and persisted to `~/.pi/wechat/owner.json`, or pinned up front via
`ownerUserId`; inspect or change it with `/wechat owner`.

One iLink account supports one poller, so the channel belongs to a single pi session. A
session that does not own it starts no worker and takes over automatically within ~30 s of
the owner closing; `/wechat start --takeover` seizes it immediately. A killed pi releases
the channel when its lease expires (~90 s).

## Compared to Hermes Agent

| Feature | Hermes Agent | Pi WeChat Extension |
|---------|:-----------:|:------------------:|
| Long-polling | ✅ asyncio Gateway | ✅ Child process |
| QR login | ✅ Built-in | ✅ Built-in |
| Markdown preservation | ✅ | ✅ |
| Sender authorization | ✅ Per-user policy | ✅ Single owner (allowlist of one) |
| Voice transcript / quote reply context | ✅ | ✅ Passed through to the agent |
| Media receive (auto-download) | ✅ Full | ✅ image/file/video/voice (first attachment only) |
| Media send | ✅ Full | ✅ via `/wechat send-media` (path/url/base64) |
| Multi-user sessions | ✅ Per-user isolation | ⚠️ Single session bridge |
| 24/7 daemon | ✅ systemd/launchd | ⚠️ Requires pi running |
| Typing indicators | ✅ | ✅ |
| Message deduplication | ✅ | ✅ |
| Context token persistence | ✅ | ✅ |
| Smart chunking | ✅ | ✅ |
| SSRF protection | ✅ | ❌ Future |
| Delivery ledger | ✅ | ✅ Bounded retry, persisted |
