diff --git a/README.fr.md b/README.fr.md
index c452b71ac1..7d1ca3e576 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -456,8 +456,6 @@ picoclaw gateway
"enabled": true,
"channel_secret": "VOTRE_CHANNEL_SECRET",
"channel_access_token": "VOTRE_CHANNEL_ACCESS_TOKEN",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18791,
"webhook_path": "/webhook/line",
"allow_from": []
}
@@ -470,12 +468,14 @@ picoclaw gateway
LINE exige HTTPS pour les webhooks. Utilisez un reverse proxy ou un tunnel :
```bash
-# Exemple avec ngrok
-ngrok http 18791
+# Exemple avec ngrok (tunnel vers le serveur Gateway partagé)
+ngrok http 18790
```
Puis configurez l'URL du Webhook dans la LINE Developers Console sur `https://votre-domaine/webhook/line` et activez **Use webhook**.
+> **Note** : Le webhook LINE est servi par le serveur Gateway partagé (par défaut `127.0.0.1:18790`). Si vous utilisez ngrok ou un proxy inverse, faites pointer le tunnel vers le port `18790`.
+
**4. Lancer**
```bash
@@ -484,7 +484,7 @@ picoclaw gateway
> Dans les discussions de groupe, le bot répond uniquement lorsqu'il est mentionné avec @. Les réponses citent le message original.
-> **Docker Compose** : Ajoutez `ports: ["18791:18791"]` au service `picoclaw-gateway` pour exposer le port du webhook.
+> **Docker Compose** : Si vous avez besoin d'exposer le webhook LINE via Docker, mappez le port du Gateway partagé (par défaut `18790`) vers l'hôte, par exemple `ports: ["18790:18790"]`. Notez que le serveur Gateway sert les webhooks de tous les canaux à partir de ce port.
@@ -515,8 +515,6 @@ Voir le [Guide de Configuration WeCom App](docs/wecom-app-configuration.md) pour
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18793,
"webhook_path": "/webhook/wecom",
"allow_from": []
}
@@ -535,7 +533,7 @@ Voir le [Guide de Configuration WeCom App](docs/wecom-app-configuration.md) pour
**2. Configurer la réception des messages**
* Dans les détails de l'application, cliquez sur "Recevoir les Messages" → "Configurer l'API"
-* Définissez l'URL sur `http://your-server:18792/webhook/wecom-app`
+* Définissez l'URL sur `http://your-server:18790/webhook/wecom-app`
* Générez le **Token** et l'**EncodingAESKey**
**3. Configurer**
@@ -550,8 +548,6 @@ Voir le [Guide de Configuration WeCom App](docs/wecom-app-configuration.md) pour
"agent_id": 1000002,
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18792,
"webhook_path": "/webhook/wecom-app",
"allow_from": []
}
@@ -565,7 +561,7 @@ Voir le [Guide de Configuration WeCom App](docs/wecom-app-configuration.md) pour
picoclaw gateway
```
-> **Note** : WeCom App nécessite l'ouverture du port 18792 pour les callbacks webhook. Utilisez un proxy inverse pour HTTPS en production.
+> **Note** : Les callbacks webhook WeCom App sont servis par le serveur Gateway partagé (par défaut `127.0.0.1:18790`). Assurez-vous que le port `18790` est accessible ou utilisez un proxy inverse HTTPS en production.
diff --git a/README.ja.md b/README.ja.md
index 6d5d09451b..553e8ab632 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -421,8 +421,6 @@ picoclaw gateway
"enabled": true,
"channel_secret": "YOUR_CHANNEL_SECRET",
"channel_access_token": "YOUR_CHANNEL_ACCESS_TOKEN",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18791,
"webhook_path": "/webhook/line",
"allow_from": []
}
@@ -436,11 +434,13 @@ LINE の Webhook には HTTPS が必要です。リバースプロキシまた
```bash
# ngrok の例
-ngrok http 18791
+ngrok http 18790
```
LINE Developers Console で Webhook URL を `https://あなたのドメイン/webhook/line` に設定し、**Webhook の利用** を有効にしてください。
+> **注意**: LINE の Webhook は共有の Gateway HTTP サーバー(デフォルト: `127.0.0.1:18790`)で提供されます。ホストからアクセスする場合は Gateway のポートを公開するか、リバースプロキシを設定してください。
+
**4. 起動**
```bash
@@ -449,7 +449,7 @@ picoclaw gateway
> グループチャットでは @メンション時のみ応答します。返信は元メッセージを引用する形式です。
-> **Docker Compose**: `picoclaw-gateway` サービスに `ports: ["18791:18791"]` を追加して Webhook ポートを公開してください。
+> **Docker Compose**: Gateway HTTP サーバーは共有の `127.0.0.1:18790` で Webhook を提供します。ホストからアクセスするには `picoclaw-gateway` サービスに `ports: ["18790:18790"]` を追加してください。
@@ -480,13 +480,13 @@ PicoClaw は2種類の WeCom 統合をサポートしています:
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18793,
"webhook_path": "/webhook/wecom",
"allow_from": []
}
}
}
+
+> **注意**: WeCom Bot の Webhook 受信は共有の Gateway HTTP サーバー(デフォルト: `127.0.0.1:18790`)で提供されます。ホストからアクセスする場合は Gateway のポートを公開するか、HTTPS 用のリバースプロキシを設定してください。
```
**クイックセットアップ - WeCom App:**
@@ -500,7 +500,7 @@ PicoClaw は2種類の WeCom 統合をサポートしています:
**2. メッセージ受信を設定**
* アプリ詳細で "メッセージを受信" → "APIを設定" をクリック
-* URL を `http://your-server:18792/webhook/wecom-app` に設定
+* URL を `http://your-server:18790/webhook/wecom-app` に設定
* **Token** と **EncodingAESKey** を生成
**3. 設定**
@@ -515,8 +515,6 @@ PicoClaw は2種類の WeCom 統合をサポートしています:
"agent_id": 1000002,
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18792,
"webhook_path": "/webhook/wecom-app",
"allow_from": []
}
@@ -530,7 +528,7 @@ PicoClaw は2種類の WeCom 統合をサポートしています:
picoclaw gateway
```
-> **注意**: WeCom App は Webhook コールバック用にポート 18792 を開放する必要があります。本番環境では HTTPS 用のリバースプロキシを使用してください。
+> **注意**: WeCom App の Webhook コールバックは共有の Gateway HTTP サーバー(デフォルト: `127.0.0.1:18790`)で提供されます。ホストからアクセスする場合は HTTPS 用のリバースプロキシを設定してください。
diff --git a/README.md b/README.md
index b040d0605b..2a253401ed 100644
--- a/README.md
+++ b/README.md
@@ -295,6 +295,8 @@ That's it! You have a working AI assistant in 2 minutes.
Talk to your picoclaw through Telegram, Discord, WhatsApp, DingTalk, LINE, or WeCom
+> **Note**: All webhook-based channels (LINE, WeCom, etc.) are served on a single shared Gateway HTTP server (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`). There are no per-channel ports to configure. Note: Feishu uses WebSocket/SDK mode and does not use the shared HTTP webhook server.
+
| Channel | Setup |
| ------------ | ---------------------------------- |
| **Telegram** | Easy (just a token) |
@@ -364,8 +366,7 @@ picoclaw gateway
"discord": {
"enabled": true,
"token": "YOUR_BOT_TOKEN",
- "allow_from": ["YOUR_USER_ID"],
- "mention_only": false
+ "allow_from": ["YOUR_USER_ID"]
}
}
}
@@ -378,9 +379,31 @@ picoclaw gateway
* Bot Permissions: `Send Messages`, `Read Message History`
* Open the generated invite URL and add the bot to your server
-**Optional: Mention-only mode**
+**Optional: Group trigger mode**
-Set `"mention_only": true` to make the bot respond only when @-mentioned. Useful for shared servers where you want the bot to respond only when explicitly called.
+By default the bot responds to all messages in a server channel. To restrict responses to @-mentions only, add:
+
+```json
+{
+ "channels": {
+ "discord": {
+ "group_trigger": { "mention_only": true }
+ }
+ }
+}
+```
+
+You can also trigger by keyword prefixes (e.g. `!bot`):
+
+```json
+{
+ "channels": {
+ "discord": {
+ "group_trigger": { "prefixes": ["!bot"] }
+ }
+ }
+}
+```
**6. Run**
@@ -501,8 +524,6 @@ picoclaw gateway
"enabled": true,
"channel_secret": "YOUR_CHANNEL_SECRET",
"channel_access_token": "YOUR_CHANNEL_ACCESS_TOKEN",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18791,
"webhook_path": "/webhook/line",
"allow_from": []
}
@@ -510,13 +531,15 @@ picoclaw gateway
}
```
+> LINE webhook is served on the shared Gateway server (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`).
+
**3. Set up Webhook URL**
LINE requires HTTPS for webhooks. Use a reverse proxy or tunnel:
```bash
-# Example with ngrok
-ngrok http 18791
+# Example with ngrok (gateway default port is 18790)
+ngrok http 18790
```
Then set the Webhook URL in LINE Developers Console to `https://your-domain/webhook/line` and enable **Use webhook**.
@@ -529,8 +552,6 @@ picoclaw gateway
> In group chats, the bot responds only when @mentioned. Replies quote the original message.
-> **Docker Compose**: Add `ports: ["18791:18791"]` to the `picoclaw-gateway` service to expose the webhook port.
-
@@ -560,8 +581,6 @@ See [WeCom App Configuration Guide](docs/wecom-app-configuration.md) for detaile
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18793,
"webhook_path": "/webhook/wecom",
"allow_from": []
}
@@ -569,6 +588,8 @@ See [WeCom App Configuration Guide](docs/wecom-app-configuration.md) for detaile
}
```
+> WeCom webhook is served on the shared Gateway server (`gateway.host`:`gateway.port`, default `127.0.0.1:18790`).
+
**Quick Setup - WeCom App:**
**1. Create an app**
@@ -576,10 +597,11 @@ See [WeCom App Configuration Guide](docs/wecom-app-configuration.md) for detaile
* Go to WeCom Admin Console → App Management → Create App
* Copy **AgentId** and **Secret**
* Go to "My Company" page, copy **CorpID**
+
**2. Configure receive message**
* In App details, click "Receive Message" → "Set API"
-* Set URL to `http://your-server:18792/webhook/wecom-app`
+* Set URL to `http://your-server:18790/webhook/wecom-app`
* Generate **Token** and **EncodingAESKey**
**3. Configure**
@@ -594,8 +616,6 @@ See [WeCom App Configuration Guide](docs/wecom-app-configuration.md) for detaile
"agent_id": 1000002,
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18792,
"webhook_path": "/webhook/wecom-app",
"allow_from": []
}
@@ -609,7 +629,7 @@ See [WeCom App Configuration Guide](docs/wecom-app-configuration.md) for detaile
picoclaw gateway
```
-> **Note**: WeCom App requires opening port 18792 for webhook callbacks. Use a reverse proxy for HTTPS.
+> **Note**: WeCom webhook callbacks are served on the Gateway port (default 18790). Use a reverse proxy for HTTPS.
diff --git a/README.pt-br.md b/README.pt-br.md
index 61663e363e..027970b974 100644
--- a/README.pt-br.md
+++ b/README.pt-br.md
@@ -450,8 +450,6 @@ picoclaw gateway
"enabled": true,
"channel_secret": "YOUR_CHANNEL_SECRET",
"channel_access_token": "YOUR_CHANNEL_ACCESS_TOKEN",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18791,
"webhook_path": "/webhook/line",
"allow_from": []
}
@@ -465,11 +463,13 @@ O LINE requer HTTPS para webhooks. Use um reverse proxy ou tunnel:
```bash
# Exemplo com ngrok
-ngrok http 18791
+ngrok http 18790
```
Em seguida, configure a Webhook URL no LINE Developers Console para `https://seu-dominio/webhook/line` e habilite **Use webhook**.
+> **Nota**: O webhook do LINE é servido pelo Gateway compartilhado (padrão 127.0.0.1:18790). Use um proxy reverso/HTTPS ou túnel (como ngrok) para expor o Gateway de forma segura quando necessário.
+
**4. Executar**
```bash
@@ -478,7 +478,7 @@ picoclaw gateway
> Em chats de grupo, o bot responde apenas quando mencionado com @. As respostas citam a mensagem original.
-> **Docker Compose**: Adicione `ports: ["18791:18791"]` ao serviço `picoclaw-gateway` para expor a porta do webhook.
+> **Docker Compose**: Se você usa Docker Compose, exponha o Gateway (padrão 127.0.0.1:18790) se precisar acessar o webhook LINE externamente, por exemplo `ports: ["18790:18790"]`.
@@ -509,8 +509,6 @@ Veja o [Guia de Configuração WeCom App](docs/wecom-app-configuration.md) para
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18793,
"webhook_path": "/webhook/wecom",
"allow_from": []
}
@@ -518,6 +516,8 @@ Veja o [Guia de Configuração WeCom App](docs/wecom-app-configuration.md) para
}
```
+> **Nota**: O webhook do WeCom Bot é atendido pelo Gateway compartilhado (padrão 127.0.0.1:18790). Use um proxy reverso/HTTPS ou túnel para expor o Gateway em produção.
+
**Configuração Rápida - WeCom App:**
**1. Criar um aplicativo**
@@ -529,7 +529,7 @@ Veja o [Guia de Configuração WeCom App](docs/wecom-app-configuration.md) para
**2. Configurar recebimento de mensagens**
* Nos detalhes do aplicativo, clique em "Receber Mensagens" → "Configurar API"
-* Defina a URL como `http://your-server:18792/webhook/wecom-app`
+* Defina a URL como `http://your-server:18790/webhook/wecom-app`
* Gere o **Token** e o **EncodingAESKey**
**3. Configurar**
@@ -544,8 +544,6 @@ Veja o [Guia de Configuração WeCom App](docs/wecom-app-configuration.md) para
"agent_id": 1000002,
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18792,
"webhook_path": "/webhook/wecom-app",
"allow_from": []
}
@@ -559,7 +557,7 @@ Veja o [Guia de Configuração WeCom App](docs/wecom-app-configuration.md) para
picoclaw gateway
```
-> **Nota**: O WeCom App requer a abertura da porta 18792 para callbacks de webhook. Use um proxy reverso para HTTPS em produção.
+> **Nota**: O WeCom App (callbacks de webhook) é servido pelo Gateway compartilhado (padrão 127.0.0.1:18790). Em produção use um proxy reverso HTTPS para expor a porta do Gateway, ou atualize `PICOCLAW_GATEWAY_HOST` para `0.0.0.0` se necessário.
diff --git a/README.vi.md b/README.vi.md
index f8ece7eda9..bfbacb0f4a 100644
--- a/README.vi.md
+++ b/README.vi.md
@@ -3,7 +3,7 @@
PicoClaw: Trợ lý AI Siêu Nhẹ viết bằng Go
-Phần cứng $10 · RAM 10MB · Khởi động 1 giây · 皮皮虾,我们走!
+Phần cứng $10 · RAM 10MB · Khởi động 1 giây · Nào, xuất phát!
@@ -424,8 +424,6 @@ picoclaw gateway
"enabled": true,
"channel_secret": "YOUR_CHANNEL_SECRET",
"channel_access_token": "YOUR_CHANNEL_ACCESS_TOKEN",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18791,
"webhook_path": "/webhook/line",
"allow_from": []
}
@@ -439,7 +437,7 @@ LINE yêu cầu HTTPS cho webhook. Sử dụng reverse proxy hoặc tunnel:
```bash
# Ví dụ với ngrok
-ngrok http 18791
+ngrok http 18790
```
Sau đó cài đặt Webhook URL trong LINE Developers Console thành `https://your-domain/webhook/line` và bật **Use webhook**.
@@ -452,7 +450,7 @@ picoclaw gateway
> Trong nhóm chat, bot chỉ phản hồi khi được @mention. Các câu trả lời sẽ trích dẫn tin nhắn gốc.
-> **Docker Compose**: Thêm `ports: ["18791:18791"]` vào service `picoclaw-gateway` để mở port webhook.
+> **Docker Compose**: Nếu bạn cần mở port webhook cục bộ, hãy thêm một rule chuyển tiếp từ port Gateway (mặc định 18790) tới host. Lưu ý: LINE webhook được phục vụ bởi Gateway HTTP chung (mặc định 127.0.0.1:18790).
@@ -483,8 +481,6 @@ Xem [Hướng dẫn Cấu hình WeCom App](docs/wecom-app-configuration.md) đ
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18793,
"webhook_path": "/webhook/wecom",
"allow_from": []
}
@@ -492,6 +488,8 @@ Xem [Hướng dẫn Cấu hình WeCom App](docs/wecom-app-configuration.md) đ
}
```
+> **Lưu ý:** Các endpoint webhook của WeCom Bot được phục vụ bởi máy chủ Gateway HTTP dùng chung (mặc định 127.0.0.1:18790). Nếu bạn cần truy cập từ bên ngoài, hãy cấu hình reverse proxy hoặc mở cổng Gateway tương ứng.
+
**Thiết lập Nhanh - WeCom App:**
**1. Tạo ứng dụng**
@@ -503,7 +501,7 @@ Xem [Hướng dẫn Cấu hình WeCom App](docs/wecom-app-configuration.md) đ
**2. Cấu hình nhận tin nhắn**
* Trong chi tiết ứng dụng, nhấp vào "Nhận Tin nhắn" → "Thiết lập API"
-* Đặt URL thành `http://your-server:18792/webhook/wecom-app`
+* Đặt URL thành `http://your-server:18790/webhook/wecom-app`
* Tạo **Token** và **EncodingAESKey**
**3. Cấu hình**
@@ -518,8 +516,6 @@ Xem [Hướng dẫn Cấu hình WeCom App](docs/wecom-app-configuration.md) đ
"agent_id": 1000002,
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18792,
"webhook_path": "/webhook/wecom-app",
"allow_from": []
}
@@ -533,7 +529,7 @@ Xem [Hướng dẫn Cấu hình WeCom App](docs/wecom-app-configuration.md) đ
picoclaw gateway
```
-> **Lưu ý**: WeCom App yêu cầu mở cổng 18792 cho callback webhook. Sử dụng proxy ngược cho HTTPS trong môi trường sản xuất.
+> **Lưu ý**: WeCom App callback webhook được phục vụ bởi Gateway HTTP chung (mặc định 127.0.0.1:18790). Sử dụng proxy ngược để cung cấp HTTPS trong môi trường production nếu cần.
diff --git a/README.zh.md b/README.zh.md
index 7c9351cb42..1d8db583e9 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -290,6 +290,8 @@ picoclaw agent -m "2+2 等于几?"
PicoClaw 支持多种聊天平台,使您的 Agent 能够连接到任何地方。
+> **注意**: 所有 Webhook 类渠道(LINE、WeCom 等)均挂载在同一个 Gateway HTTP 服务器上(`gateway.host`:`gateway.port`,默认 `127.0.0.1:18790`),无需为每个渠道单独配置端口。注意:飞书(Feishu)使用 WebSocket/SDK 模式,不通过该共享 HTTP webhook 服务器接收消息。
+
### 核心渠道
| 渠道 | 设置难度 | 特性说明 | 文档链接 |
diff --git a/config/config.example.json b/config/config.example.json
index 55a823009e..d885ef94b0 100644
--- a/config/config.example.json
+++ b/config/config.example.json
@@ -59,7 +59,9 @@
"enabled": false,
"token": "YOUR_DISCORD_BOT_TOKEN",
"allow_from": [],
- "mention_only": false,
+ "group_trigger": {
+ "mention_only": false
+ },
"reasoning_channel_id": ""
},
"qq": {
@@ -111,8 +113,6 @@
"enabled": false,
"channel_secret": "YOUR_LINE_CHANNEL_SECRET",
"channel_access_token": "YOUR_LINE_CHANNEL_ACCESS_TOKEN",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18791,
"webhook_path": "/webhook/line",
"allow_from": [],
"reasoning_channel_id": ""
@@ -132,8 +132,6 @@
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18793,
"webhook_path": "/webhook/wecom",
"allow_from": [],
"reply_timeout": 5,
@@ -147,8 +145,6 @@
"agent_id": 1000002,
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_43_CHAR_ENCODING_AES_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18792,
"webhook_path": "/webhook/wecom-app",
"allow_from": [],
"reply_timeout": 5,
diff --git a/docs/channels/discord/README.zh.md b/docs/channels/discord/README.zh.md
index 5b597eced6..6d3c502cf6 100644
--- a/docs/channels/discord/README.zh.md
+++ b/docs/channels/discord/README.zh.md
@@ -11,7 +11,9 @@ Discord 是一个专为社区设计的免费语音、视频和文本聊天应用
"enabled": true,
"token": "YOUR_BOT_TOKEN",
"allow_from": ["YOUR_USER_ID"],
- "mention_only": false
+ "group_trigger": {
+ "mention_only": false
+ }
}
}
}
@@ -22,7 +24,7 @@ Discord 是一个专为社区设计的免费语音、视频和文本聊天应用
| enabled | bool | 是 | 是否启用 Discord 频道 |
| token | string | 是 | Discord 机器人 Token |
| allow_from | array | 否 | 用户ID白名单,空表示允许所有用户 |
-| mention_only | bool | 否 | 是否仅响应提及机器人的消息 |
+| group_trigger | object | 否 | 群组触发设置(示例: { "mention_only": false }) |
## 设置流程
diff --git a/docs/channels/line/README.zh.md b/docs/channels/line/README.zh.md
index fd3aa80da1..a36f622c2f 100644
--- a/docs/channels/line/README.zh.md
+++ b/docs/channels/line/README.zh.md
@@ -11,8 +11,6 @@ PicoClaw 通过 LINE Messaging API 配合 Webhook 回调功能实现对 LINE 的
"enabled": true,
"channel_secret": "YOUR_CHANNEL_SECRET",
"channel_access_token": "YOUR_CHANNEL_ACCESS_TOKEN",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18791,
"webhook_path": "/webhook/line",
"allow_from": []
}
@@ -25,9 +23,7 @@ PicoClaw 通过 LINE Messaging API 配合 Webhook 回调功能实现对 LINE 的
| enabled | bool | 是 | 是否启用 LINE Channel |
| channel_secret | string | 是 | LINE Messaging API 的 Channel Secret |
| channel_access_token | string | 是 | LINE Messaging API 的 Channel Access Token |
-| webhook_host | string | 是 | Webhook 监听的主机地址 (通常为 0.0.0.0) |
-| webhook_port | int | 是 | Webhook 监听的端口 (默认为 18791) |
-| webhook_path | string | 是 | Webhook 的路径 (默认为 /webhook/line) |
+| webhook_path | string | 否 | Webhook 的路径 (默认为 /webhook/line) |
| allow_from | array | 否 | 用户ID白名单,空表示允许所有用户 |
## 设置流程
@@ -35,7 +31,8 @@ PicoClaw 通过 LINE Messaging API 配合 Webhook 回调功能实现对 LINE 的
1. 前往 [LINE Developers Console](https://developers.line.biz/console/) 创建一个服务提供商和一个 Messaging API Channel
2. 获取 Channel Secret 和 Channel Access Token
3. 配置Webhook:
- - Line要求Webhook必须使用HTTPS协议,因此需要部署一个支持HTTPS的服务器,或者使用反向代理工具如ngrok将本地服务器暴露到公网
- - 将 Webhook URL 设置为 `https://your-domain.com/webhook/line`
+ - LINE 要求 Webhook 必须使用 HTTPS 协议,因此需要部署一个支持 HTTPS 的服务器,或者使用反向代理工具如 ngrok 将本地服务器暴露到公网
+ - PicoClaw 现在使用共享的 Gateway HTTP 服务器来接收所有渠道的 webhook 回调,默认监听地址为 127.0.0.1:18790
+ - 将 Webhook URL 设置为 `https://your-domain.com/webhook/line`,然后将外部域名反向代理到本机的 Gateway(默认端口 18790)
- 启用 Webhook 并验证 URL
4. 将 Channel Secret 和 Channel Access Token 填入配置文件中
diff --git a/docs/channels/wecom/wecom_app/README.zh.md b/docs/channels/wecom/wecom_app/README.zh.md
index 1e6a0e2b36..0a98581079 100644
--- a/docs/channels/wecom/wecom_app/README.zh.md
+++ b/docs/channels/wecom/wecom_app/README.zh.md
@@ -14,8 +14,6 @@
"agent_id": 1000002,
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18792,
"webhook_path": "/webhook/wecom-app",
"allow_from": [],
"reply_timeout": 5
@@ -31,8 +29,6 @@
| agent_id | int | 是 | 应用程序代理 ID |
| token | string | 是 | 回调验证令牌 |
| encoding_aes_key | string | 是 | 43 字符 AES 密钥 |
-| webhook_host | string | 否 | HTTP 服务器绑定地址 |
-| webhook_port | int | 否 | HTTP 服务器端口(默认:18792) |
| webhook_path | string | 否 | Webhook 路径(默认:/webhook/wecom-app) |
| allow_from | array | 否 | 用户 ID 白名单 |
| reply_timeout | int | 否 | 回复超时时间(秒) |
@@ -45,3 +41,5 @@
4. 在应用设置中配置“接收消息”,获取 Token 和 EncodingAESKey
5. 设置回调 URL 为 `http://:/webhook/wecom-app`
6. 将 CorpID, Secret, AgentID 等信息填入配置文件
+
+ 注意: PicoClaw 现在使用共享的 Gateway HTTP 服务器来接收所有渠道的 webhook 回调,默认监听地址为 127.0.0.1:18790。如需从公网接收回调,请把外部域名反向代理到 Gateway(默认端口 18790)。
diff --git a/docs/channels/wecom/wecom_bot/README.zh.md b/docs/channels/wecom/wecom_bot/README.zh.md
index c4bb1c87e7..63d9b84d68 100644
--- a/docs/channels/wecom/wecom_bot/README.zh.md
+++ b/docs/channels/wecom/wecom_bot/README.zh.md
@@ -12,8 +12,6 @@
"token": "YOUR_TOKEN",
"encoding_aes_key": "YOUR_ENCODING_AES_KEY",
"webhook_url": "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY",
- "webhook_host": "0.0.0.0",
- "webhook_port": 18793,
"webhook_path": "/webhook/wecom",
"allow_from": [],
"reply_timeout": 5
@@ -27,8 +25,6 @@
| token | string | 是 | 签名验证代币 |
| encoding_aes_key | string | 是 | 用于解密的 43 字符 AES 密钥 |
| webhook_url | string | 是 | 用于发送回复的企业微信群聊机器人 Webhook URL |
-| webhook_host | string | 否 | HTTP 服务器绑定地址(默认:0.0.0.0) |
-| webhook_port | int | 否 | HTTP 服务器端口(默认:18793) |
| webhook_path | string | 否 | Webhook 端点路径(默认:/webhook/wecom) |
| allow_from | array | 否 | 用户 ID 白名单(空值 = 允许所有用户) |
| reply_timeout | int | 否 | 回复超时时间(单位:秒,默认值:5) |
@@ -39,3 +35,5 @@
2. 获取 Webhook URL
3. (如需接收消息) 在机器人配置页面设置接收消息的 API 地址(回调地址)以及 Token 和 EncodingAESKey
4. 将相关信息填入配置文件
+
+ 注意: PicoClaw 现在使用共享的 Gateway HTTP 服务器来接收所有渠道的 webhook 回调,默认监听地址为 127.0.0.1:18790。如需从公网接收回调,请把外部域名反向代理到 Gateway(默认端口 18790)。
diff --git a/docs/wecom-app-configuration.md b/docs/wecom-app-configuration.md
index 3b17d37a74..3c720ecd1d 100644
--- a/docs/wecom-app-configuration.md
+++ b/docs/wecom-app-configuration.md
@@ -26,7 +26,7 @@
1. 在应用详情页,点击"接收消息"的"设置API接收"
2. 填写以下信息:
- - **URL**: `http://your-server:18792/webhook/wecom-app`
+ - **URL**: `http://your-server:18790/webhook/wecom-app`
- **Token**: 随机生成或自定义(用于签名验证)
- **EncodingAESKey**: 点击"随机生成"生成43字符的密钥
3. 点击"保存"时,企业微信会发送验证请求
@@ -45,8 +45,6 @@
"agent_id": 1000002, // 应用AgentId
"token": "your_token", // 接收消息配置的Token
"encoding_aes_key": "your_encoding_aes_key", // 接收消息配置的EncodingAESKey
- "webhook_host": "0.0.0.0",
- "webhook_port": 18792,
"webhook_path": "/webhook/wecom-app",
"allow_from": [],
"reply_timeout": 5
@@ -62,7 +60,7 @@
**症状**: 企业微信保存API接收消息时提示验证失败
**检查项**:
-- 确认服务器防火墙已开放 18792 端口
+- 确认服务器防火墙已开放 Gateway 端口(默认 18790)
- 确认 `corp_id`、`token`、`encoding_aes_key` 配置正确
- 查看 PicoClaw 日志是否有请求到达
@@ -78,7 +76,7 @@
**症状**: 启动时提示端口已被占用
-**解决**: 修改 `webhook_port` 为其他端口,如 18794
+**解决**: 修改 `gateway.port` 为其他端口(所有 Webhook 渠道共享同一个 Gateway HTTP 服务器)
## 技术细节
diff --git a/pkg/channels/README.md b/pkg/channels/README.md
index 52b9f98f4d..b7c56660bf 100644
--- a/pkg/channels/README.md
+++ b/pkg/channels/README.md
@@ -1,7 +1,5 @@
-# PicoClaw Channel System Refactor: Complete Development Guide
+# PicoClaw Channel System: Complete Development Guide
-> **Branch**: `refactor/channel-system`
-> **Status**: Active development (~40 commits)
> **Scope**: `pkg/channels/`, `pkg/bus/`, `pkg/media/`, `pkg/identity/`, `cmd/picoclaw/internal/gateway/`
---
@@ -46,6 +44,8 @@ pkg/channels/
pkg/channels/
├── base.go # BaseChannel shared abstraction layer
├── interfaces.go # Optional capability interfaces (TypingCapable, MessageEditor, ReactionCapable, PlaceholderCapable, PlaceholderRecorder)
+├── README.md # English documentation
+├── README.zh.md # Chinese documentation
├── media.go # MediaSender optional interface
├── webhook.go # WebhookHandler, HealthChecker optional interfaces
├── errors.go # Sentinel errors (ErrNotRunning, ErrRateLimit, ErrTemporary, ErrSendFailed)
@@ -60,7 +60,7 @@ pkg/channels/
├── discord/
│ ├── init.go
│ └── discord.go
-├── slack/ line/ onebot/ dingtalk/ feishu/ wecom/ qq/ whatsapp/ maixcam/ pico/
+├── slack/ line/ onebot/ dingtalk/ feishu/ wecom/ qq/ whatsapp/ whatsapp_native/ maixcam/ pico/
│ └── ...
pkg/bus/
@@ -111,7 +111,7 @@ pkg/identity/
|-----------|-------------|
| **Sub-package Isolation** | Each channel is a standalone Go sub-package, depending on `BaseChannel` and interfaces from the `channels` parent package |
| **Factory Registration** | Sub-packages self-register via `init()`, Manager looks up factories by name, eliminating import coupling |
-| **Capability Discovery** | Optional capabilities are declared via interfaces (`MediaSender`, `TypingCapable`, `ReactionCapable`, `PlaceholderCapable`, `MessageEditor`, `WebhookHandler`), discovered by Manager via runtime type assertions |
+| **Capability Discovery** | Optional capabilities are declared via interfaces (`MediaSender`, `TypingCapable`, `ReactionCapable`, `PlaceholderCapable`, `MessageEditor`, `WebhookHandler`, `HealthChecker`), discovered by Manager via runtime type assertions |
| **Structured Messages** | Peer, MessageID, and SenderInfo promoted from Metadata to first-class fields on InboundMessage |
| **Error Classification** | Channels return sentinel errors (`ErrRateLimit`, `ErrTemporary`, etc.), Manager uses these to determine retry strategy |
| **Centralized Orchestration** | Rate limiting, message splitting, retries, and Typing/Reaction/Placeholder management are all handled by Manager and BaseChannel; channels only need to implement Send |
@@ -145,6 +145,7 @@ After refactoring, these files have been removed and code moved to corresponding
| _(did not exist)_ | `pkg/channels/interfaces.go` | New optional capability interfaces |
| _(did not exist)_ | `pkg/channels/media.go` | New MediaSender interface |
| _(did not exist)_ | `pkg/channels/webhook.go` | New WebhookHandler/HealthChecker |
+| _(did not exist)_ | `pkg/channels/whatsapp_native/` | New WhatsApp native mode (whatsmeow) |
| _(did not exist)_ | `pkg/channels/split.go` | New message splitting (migrated from utils) |
| _(did not exist)_ | `pkg/bus/types.go` | New structured message types |
| _(did not exist)_ | `pkg/media/store.go` | New media file lifecycle management |
@@ -220,6 +221,7 @@ func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChann
cfg.Channels.Telegram.AllowFrom, // Allow list
channels.WithMaxMessageLength(4096), // Platform message length limit
channels.WithGroupTrigger(cfg.Channels.Telegram.GroupTrigger), // Group trigger config
+ channels.WithReasoningChannelID(cfg.Channels.Telegram.ReasoningChannelID), // Reasoning chain routing
)
return &TelegramChannel{
BaseChannel: base,
@@ -466,6 +468,7 @@ func NewMatrixChannel(cfg *config.Config, msgBus *bus.MessageBus) (*MatrixChanne
matrixCfg.AllowFrom, // Allow list
channels.WithMaxMessageLength(65536), // Matrix message length limit
channels.WithGroupTrigger(matrixCfg.GroupTrigger),
+ channels.WithReasoningChannelID(matrixCfg.ReasoningChannelID), // Reasoning chain routing (optional)
)
return &MatrixChannel{
@@ -666,6 +669,32 @@ func (c *MatrixChannel) EditMessage(ctx context.Context, chatID, messageID, cont
}
```
+#### PlaceholderCapable — Placeholder Messages
+
+```go
+// If the platform supports sending placeholder messages (e.g. "Thinking... 💭"),
+// and the channel also implements MessageEditor, then Manager's preSend will
+// automatically edit the placeholder into the final response on outbound.
+// SendPlaceholder checks PlaceholderConfig.Enabled internally;
+// returning ("", nil) means skip.
+func (c *MatrixChannel) SendPlaceholder(ctx context.Context, chatID string) (string, error) {
+ cfg := c.config.Channels.Matrix.Placeholder
+ if !cfg.Enabled {
+ return "", nil
+ }
+ text := cfg.Text
+ if text == "" {
+ text = "Thinking... 💭"
+ }
+ // Call Matrix API to send placeholder message
+ msg, err := c.sendText(ctx, chatID, text)
+ if err != nil {
+ return "", err
+ }
+ return msg.ID, nil
+}
+```
+
#### WebhookHandler — HTTP Webhook Reception
```go
@@ -746,15 +775,17 @@ When the Agent finishes processing a message, Manager's `preSend` automatically:
```go
type ChannelsConfig struct {
// ... existing channels
- Matrix MatrixChannelConfig `yaml:"matrix" json:"matrix"`
+ Matrix MatrixChannelConfig `json:"matrix"`
}
type MatrixChannelConfig struct {
- Enabled bool `yaml:"enabled" json:"enabled"`
- HomeServer string `yaml:"home_server" json:"home_server"`
- Token string `yaml:"token" json:"token"`
- AllowFrom []string `yaml:"allow_from" json:"allow_from"`
- GroupTrigger GroupTriggerConfig `yaml:"group_trigger" json:"group_trigger"`
+ Enabled bool `json:"enabled"`
+ HomeServer string `json:"home_server"`
+ Token string `json:"token"`
+ AllowFrom []string `json:"allow_from"`
+ GroupTrigger GroupTriggerConfig `json:"group_trigger"`
+ Placeholder PlaceholderConfig `json:"placeholder"`
+ ReasoningChannelID string `json:"reasoning_channel_id"`
}
```
@@ -767,6 +798,15 @@ if m.config.Channels.Matrix.Enabled && m.config.Channels.Matrix.Token != "" {
}
```
+> **Note**: If your channel has multiple modes (like WhatsApp Bridge vs Native), branch in initChannels based on config:
+> ```go
+> if cfg.UseNative {
+> m.initChannel("whatsapp_native", "WhatsApp Native")
+> } else {
+> m.initChannel("whatsapp", "WhatsApp")
+> }
+> ```
+
#### Add blank import in Gateway
```go
@@ -882,19 +922,21 @@ BaseChannel is the shared abstraction layer for all channels, providing the foll
| `IsRunning() bool` | Atomically read running state |
| `SetRunning(bool)` | Atomically set running state |
| `MaxMessageLength() int` | Message length limit (rune count), 0 = unlimited |
+| `ReasoningChannelID() string` | Reasoning chain routing target channel ID (empty = no routing) |
| `IsAllowed(senderID string) bool` | Legacy allow-list check (supports `"id\|username"` and `"@username"` formats) |
| `IsAllowedSender(sender SenderInfo) bool` | New allow-list check (delegates to `identity.MatchAllowed`) |
| `ShouldRespondInGroup(isMentioned, content) (bool, string)` | Unified group chat trigger filtering logic |
-| `HandleMessage(...)` | Unified inbound message handling: permission check → build MediaScope → auto-trigger Typing/Reaction → publish to Bus |
+| `HandleMessage(...)` | Unified inbound message handling: permission check → build MediaScope → auto-trigger Typing/Reaction/Placeholder → publish to Bus |
| `SetMediaStore(s) / GetMediaStore()` | MediaStore injected by Manager |
| `SetPlaceholderRecorder(r) / GetPlaceholderRecorder()` | PlaceholderRecorder injected by Manager |
-| `SetOwner(ch)` | Concrete channel reference injected by Manager (used for Typing/Reaction type assertions in HandleMessage) |
+| `SetOwner(ch)` | Concrete channel reference injected by Manager (used for Typing/Reaction/Placeholder type assertions in HandleMessage) |
**Functional Options**:
```go
channels.WithMaxMessageLength(4096) // Set platform message length limit
channels.WithGroupTrigger(groupTriggerCfg) // Set group trigger configuration
+channels.WithReasoningChannelID(id) // Set reasoning chain routing target channel
```
### 4.4 Factory Registry
@@ -998,7 +1040,7 @@ StartAll:
- runMediaWorker (per-channel outbound media)
- dispatchOutbound (route from bus to worker queues)
- dispatchOutboundMedia (route from bus to media worker queues)
- - runTTLJanitor (every 10s clean up expired typing/placeholder)
+ - runTTLJanitor (every 10s clean up expired typing/reaction/placeholder)
4. Start shared HTTP server (if configured)
StopAll:
@@ -1206,18 +1248,20 @@ make test # Full test suite
| Sub-package | Registered Name | Optional Interfaces |
|-------------|----------------|-------------------|
-| `pkg/channels/telegram/` | `"telegram"` | MessageEditor, MediaSender, TypingCapable, PlaceholderCapable |
-| `pkg/channels/discord/` | `"discord"` | MessageEditor, TypingCapable, PlaceholderCapable |
-| `pkg/channels/slack/` | `"slack"` | ReactionCapable |
-| `pkg/channels/line/` | `"line"` | WebhookHandler, HealthChecker, TypingCapable |
-| `pkg/channels/onebot/` | `"onebot"` | ReactionCapable |
-| `pkg/channels/dingtalk/` | `"dingtalk"` | WebhookHandler |
-| `pkg/channels/feishu/` | `"feishu"` | WebhookHandler (architecture-specific build tags) |
-| `pkg/channels/wecom/` | `"wecom"` + `"wecom_app"` | WebhookHandler |
+| `pkg/channels/telegram/` | `"telegram"` | TypingCapable, PlaceholderCapable, MessageEditor, MediaSender |
+| `pkg/channels/discord/` | `"discord"` | TypingCapable, PlaceholderCapable, MessageEditor, MediaSender |
+| `pkg/channels/slack/` | `"slack"` | ReactionCapable, MediaSender |
+| `pkg/channels/line/` | `"line"` | TypingCapable, MediaSender, WebhookHandler |
+| `pkg/channels/onebot/` | `"onebot"` | ReactionCapable, MediaSender |
+| `pkg/channels/dingtalk/` | `"dingtalk"` | — |
+| `pkg/channels/feishu/` | `"feishu"` | — (architecture-specific build tags: `feishu_32.go` / `feishu_64.go`) |
+| `pkg/channels/wecom/` | `"wecom"` | WebhookHandler, HealthChecker |
+| `pkg/channels/wecom/` | `"wecom_app"` | MediaSender, WebhookHandler, HealthChecker |
| `pkg/channels/qq/` | `"qq"` | — |
-| `pkg/channels/whatsapp/` | `"whatsapp"` | — |
+| `pkg/channels/whatsapp/` | `"whatsapp"` | — (Bridge mode) |
+| `pkg/channels/whatsapp_native/` | `"whatsapp_native"` | — (Native whatsmeow mode) |
| `pkg/channels/maixcam/` | `"maixcam"` | — |
-| `pkg/channels/pico/` | `"pico"` | WebhookHandler (Pico Protocol), TypingCapable, PlaceholderCapable |
+| `pkg/channels/pico/` | `"pico"` | TypingCapable, PlaceholderCapable, MessageEditor, WebhookHandler |
### A.3 Interface Quick Reference
@@ -1231,6 +1275,7 @@ type Channel interface {
IsRunning() bool
IsAllowed(senderID string) bool
IsAllowedSender(sender bus.SenderInfo) bool
+ ReasoningChannelID() string
}
// ===== Optional =====
@@ -1324,8 +1369,16 @@ agentLoop.Stop() // Stop Agent
1. **Media cleanup temporarily disabled**: The `ReleaseAll` call in the Agent loop is commented out (`refactor(loop): disable media cleanup to prevent premature file deletion`) because session boundaries are not yet clearly defined. TTL cleanup remains active.
-2. **Feishu architecture-specific compilation**: The Feishu channel uses build tags to distinguish 32-bit and 64-bit architectures (`feishu_32.go` / `feishu_64.go`).
+2. **Feishu architecture-specific compilation**: The Feishu channel uses build tags to distinguish 32-bit and 64-bit architectures (`feishu_32.go` / `feishu_64.go`). Feishu uses the SDK's WebSocket mode (not HTTP webhook), so it does not implement `WebhookHandler`.
+
+3. **WeCom has two factories**: `"wecom"` (Bot mode, webhook only) and `"wecom_app"` (App mode, supports MediaSender) are registered separately. Both implement `WebhookHandler` and `HealthChecker`.
+
+4. **Pico Protocol**: `pkg/channels/pico/` implements a custom PicoClaw native protocol channel that receives messages via WebSocket webhook (`/pico/ws`).
+
+5. **WhatsApp has two modes**: `"whatsapp"` (Bridge mode, communicates via external bridge URL) and `"whatsapp_native"` (native whatsmeow mode, connects directly to WhatsApp). Manager selects which to initialize based on `WhatsAppConfig.UseNative`.
+
+6. **DingTalk uses Stream mode**: DingTalk uses the SDK's Stream/WebSocket mode (not HTTP webhook), so it does not implement `WebhookHandler`.
-3. **WeCom has two factories**: `"wecom"` (Bot mode) and `"wecom_app"` (App mode) are registered separately.
+7. **PlaceholderConfig vs implementation**: `PlaceholderConfig` appears in 6 channel configs (Telegram, Discord, Slack, LINE, OneBot, Pico), but only channels that implement both `PlaceholderCapable` + `MessageEditor` (Telegram, Discord, Pico) can actually use placeholder message editing. The rest are reserved fields.
-4. **Pico Protocol**: `pkg/channels/pico/` implements a custom PicoClaw native protocol channel that receives messages via webhook.
\ No newline at end of file
+8. **ReasoningChannelID**: Most channel configs include a `reasoning_channel_id` field to route LLM reasoning/thinking output to a designated channel (WhatsApp, Telegram, Feishu, Discord, MaixCam, QQ, DingTalk, Slack, LINE, OneBot, WeCom, WeComApp). Note: `PicoConfig` does not currently expose this field. `BaseChannel` exposes this via the `WithReasoningChannelID` option and `ReasoningChannelID()` method.
\ No newline at end of file
diff --git a/pkg/channels/README.zh.md b/pkg/channels/README.zh.md
index 0a9487cd07..2c5e7356ee 100644
--- a/pkg/channels/README.zh.md
+++ b/pkg/channels/README.zh.md
@@ -1,7 +1,5 @@
-# PicoClaw Channel System 重构:完整开发指南
+# PicoClaw Channel System:完整开发指南
-> **分支**: `refactor/channel-system`
-> **状态**: 活跃开发中(约 40 commits)
> **影响范围**: `pkg/channels/`, `pkg/bus/`, `pkg/media/`, `pkg/identity/`, `cmd/picoclaw/internal/gateway/`
---
@@ -46,6 +44,8 @@ pkg/channels/
pkg/channels/
├── base.go # BaseChannel 共享抽象层
├── interfaces.go # 可选能力接口(TypingCapable, MessageEditor, ReactionCapable, PlaceholderCapable, PlaceholderRecorder)
+├── README.md # 英文文档
+├── README.zh.md # 中文文档
├── media.go # MediaSender 可选接口
├── webhook.go # WebhookHandler, HealthChecker 可选接口
├── errors.go # 错误哨兵值(ErrNotRunning, ErrRateLimit, ErrTemporary, ErrSendFailed)
@@ -60,7 +60,7 @@ pkg/channels/
├── discord/
│ ├── init.go
│ └── discord.go
-├── slack/ line/ onebot/ dingtalk/ feishu/ wecom/ qq/ whatsapp/ maixcam/ pico/
+├── slack/ line/ onebot/ dingtalk/ feishu/ wecom/ qq/ whatsapp/ whatsapp_native/ maixcam/ pico/
│ └── ...
pkg/bus/
@@ -111,7 +111,7 @@ pkg/identity/
|------|------|
| **子包隔离** | 每个 channel 一个独立 Go 子包,依赖 `channels` 父包提供的 `BaseChannel` 和接口 |
| **工厂注册** | 各子包通过 `init()` 自注册,Manager 通过名字查找工厂,消除 import 耦合 |
-| **能力发现** | 可选能力通过接口(`MediaSender`, `TypingCapable`, `ReactionCapable`, `PlaceholderCapable`, `MessageEditor`, `WebhookHandler`)声明,Manager 运行时类型断言发现 |
+| **能力发现** | 可选能力通过接口(`MediaSender`, `TypingCapable`, `ReactionCapable`, `PlaceholderCapable`, `MessageEditor`, `WebhookHandler`, `HealthChecker`)声明,Manager 运行时类型断言发现 |
| **结构化消息** | Peer、MessageID、SenderInfo 从 Metadata 提升为 InboundMessage 的一等字段 |
| **错误分类** | Channel 返回哨兵错误(`ErrRateLimit`, `ErrTemporary` 等),Manager 据此决定重试策略 |
| **集中编排** | 速率限制、消息分割、重试、Typing/Reaction/Placeholder 全部由 Manager 和 BaseChannel 统一处理,Channel 只负责 Send |
@@ -145,6 +145,7 @@ pkg/identity/
| _(不存在)_ | `pkg/channels/interfaces.go` | 新增可选能力接口 |
| _(不存在)_ | `pkg/channels/media.go` | 新增 MediaSender 接口 |
| _(不存在)_ | `pkg/channels/webhook.go` | 新增 WebhookHandler/HealthChecker |
+| _(不存在)_ | `pkg/channels/whatsapp_native/` | 新增 WhatsApp 原生模式(whatsmeow) |
| _(不存在)_ | `pkg/channels/split.go` | 新增消息分割(从 utils 迁入) |
| _(不存在)_ | `pkg/bus/types.go` | 新增结构化消息类型 |
| _(不存在)_ | `pkg/media/store.go` | 新增媒体文件生命周期管理 |
@@ -220,6 +221,7 @@ func NewTelegramChannel(cfg *config.Config, bus *bus.MessageBus) (*TelegramChann
cfg.Channels.Telegram.AllowFrom, // 允许列表
channels.WithMaxMessageLength(4096), // 平台消息长度上限
channels.WithGroupTrigger(cfg.Channels.Telegram.GroupTrigger), // 群聊触发配置
+ channels.WithReasoningChannelID(cfg.Channels.Telegram.ReasoningChannelID), // 思维链路由
)
return &TelegramChannel{
BaseChannel: base,
@@ -466,6 +468,7 @@ func NewMatrixChannel(cfg *config.Config, msgBus *bus.MessageBus) (*MatrixChanne
matrixCfg.AllowFrom, // 允许列表
channels.WithMaxMessageLength(65536), // Matrix 消息长度限制
channels.WithGroupTrigger(matrixCfg.GroupTrigger),
+ channels.WithReasoningChannelID(matrixCfg.ReasoningChannelID), // 思维链路由(可选)
)
return &MatrixChannel{
@@ -666,6 +669,31 @@ func (c *MatrixChannel) EditMessage(ctx context.Context, chatID, messageID, cont
}
```
+#### PlaceholderCapable — 占位消息
+
+```go
+// 如果平台支持发送占位消息(如 "Thinking... 💭"),并且实现了 MessageEditor,
+// 则 Manager 的 preSend 会在出站时自动将占位消息编辑为最终回复。
+// SendPlaceholder 内部根据 PlaceholderConfig.Enabled 决定是否发送;
+// 返回 ("", nil) 表示跳过。
+func (c *MatrixChannel) SendPlaceholder(ctx context.Context, chatID string) (string, error) {
+ cfg := c.config.Channels.Matrix.Placeholder
+ if !cfg.Enabled {
+ return "", nil
+ }
+ text := cfg.Text
+ if text == "" {
+ text = "Thinking... 💭"
+ }
+ // 调用 Matrix API 发送占位消息
+ msg, err := c.sendText(ctx, chatID, text)
+ if err != nil {
+ return "", err
+ }
+ return msg.ID, nil
+}
+```
+
#### WebhookHandler — HTTP Webhook 接收
```go
@@ -746,15 +774,17 @@ if c.owner != nil && c.placeholderRecorder != nil {
```go
type ChannelsConfig struct {
// ... 现有 channels
- Matrix MatrixChannelConfig `yaml:"matrix" json:"matrix"`
+ Matrix MatrixChannelConfig `json:"matrix"`
}
type MatrixChannelConfig struct {
- Enabled bool `yaml:"enabled" json:"enabled"`
- HomeServer string `yaml:"home_server" json:"home_server"`
- Token string `yaml:"token" json:"token"`
- AllowFrom []string `yaml:"allow_from" json:"allow_from"`
- GroupTrigger GroupTriggerConfig `yaml:"group_trigger" json:"group_trigger"`
+ Enabled bool `json:"enabled"`
+ HomeServer string `json:"home_server"`
+ Token string `json:"token"`
+ AllowFrom []string `json:"allow_from"`
+ GroupTrigger GroupTriggerConfig `json:"group_trigger"`
+ Placeholder PlaceholderConfig `json:"placeholder"`
+ ReasoningChannelID string `json:"reasoning_channel_id"`
}
```
@@ -767,6 +797,15 @@ if m.config.Channels.Matrix.Enabled && m.config.Channels.Matrix.Token != "" {
}
```
+> **注意**:如果你的 channel 有多种模式(如 WhatsApp Bridge vs Native),需要在 initChannels 中根据配置分支:
+> ```go
+> if cfg.UseNative {
+> m.initChannel("whatsapp_native", "WhatsApp Native")
+> } else {
+> m.initChannel("whatsapp", "WhatsApp")
+> }
+> ```
+
#### 在 Gateway 中添加 blank import
```go
@@ -882,19 +921,21 @@ BaseChannel 是所有 channel 的共享抽象层,提供以下能力:
| `IsRunning() bool` | 原子读取运行状态 |
| `SetRunning(bool)` | 原子设置运行状态 |
| `MaxMessageLength() int` | 消息长度限制(rune 计数),0 = 无限制 |
+| `ReasoningChannelID() string` | 思维链路由目标 channel ID(空 = 不路由) |
| `IsAllowed(senderID string) bool` | 旧格式允许列表检查(支持 `"id\|username"` 和 `"@username"` 格式) |
| `IsAllowedSender(sender SenderInfo) bool` | 新格式允许列表检查(委托给 `identity.MatchAllowed`) |
| `ShouldRespondInGroup(isMentioned, content) (bool, string)` | 统一群聊触发过滤逻辑 |
-| `HandleMessage(...)` | 统一入站消息处理:权限检查 → 构建 MediaScope → 自动触发 Typing/Reaction → 发布到 Bus |
+| `HandleMessage(...)` | 统一入站消息处理:权限检查 → 构建 MediaScope → 自动触发 Typing/Reaction/Placeholder → 发布到 Bus |
| `SetMediaStore(s) / GetMediaStore()` | Manager 注入的媒体存储 |
| `SetPlaceholderRecorder(r) / GetPlaceholderRecorder()` | Manager 注入的占位符记录器 |
-| `SetOwner(ch) ` | Manager 注入的具体 channel 引用(用于 HandleMessage 内部的 Typing/Reaction 类型断言) |
+| `SetOwner(ch) ` | Manager 注入的具体 channel 引用(用于 HandleMessage 内部的 Typing/Reaction/Placeholder 类型断言) |
**功能选项**:
```go
channels.WithMaxMessageLength(4096) // 设置平台消息长度限制
channels.WithGroupTrigger(groupTriggerCfg) // 设置群聊触发配置
+channels.WithReasoningChannelID(id) // 设置思维链路由目标 channel
```
### 4.4 工厂注册表
@@ -998,7 +1039,7 @@ StartAll:
- runMediaWorker (per-channel 出站媒体)
- dispatchOutbound (从 bus 路由到 worker 队列)
- dispatchOutboundMedia (从 bus 路由到 media worker 队列)
- - runTTLJanitor (每 10s 清理过期 typing/placeholder)
+ - runTTLJanitor (每 10s 清理过期 typing/reaction/placeholder)
4. 启动共享 HTTP 服务器(如已配置)
StopAll:
@@ -1206,18 +1247,20 @@ make test # 全量测试
| 子包 | 注册名 | 可选接口 |
|------|--------|----------|
-| `pkg/channels/telegram/` | `"telegram"` | MessageEditor, MediaSender, TypingCapable, PlaceholderCapable |
-| `pkg/channels/discord/` | `"discord"` | MessageEditor, TypingCapable, PlaceholderCapable |
-| `pkg/channels/slack/` | `"slack"` | ReactionCapable |
-| `pkg/channels/line/` | `"line"` | WebhookHandler, HealthChecker, TypingCapable |
-| `pkg/channels/onebot/` | `"onebot"` | ReactionCapable |
-| `pkg/channels/dingtalk/` | `"dingtalk"` | WebhookHandler |
-| `pkg/channels/feishu/` | `"feishu"` | WebhookHandler (架构特定 build tags) |
-| `pkg/channels/wecom/` | `"wecom"` + `"wecom_app"` | WebhookHandler |
+| `pkg/channels/telegram/` | `"telegram"` | TypingCapable, PlaceholderCapable, MessageEditor, MediaSender |
+| `pkg/channels/discord/` | `"discord"` | TypingCapable, PlaceholderCapable, MessageEditor, MediaSender |
+| `pkg/channels/slack/` | `"slack"` | ReactionCapable, MediaSender |
+| `pkg/channels/line/` | `"line"` | TypingCapable, MediaSender, WebhookHandler |
+| `pkg/channels/onebot/` | `"onebot"` | ReactionCapable, MediaSender |
+| `pkg/channels/dingtalk/` | `"dingtalk"` | — |
+| `pkg/channels/feishu/` | `"feishu"` | — (架构特定 build tags: `feishu_32.go` / `feishu_64.go`) |
+| `pkg/channels/wecom/` | `"wecom"` | WebhookHandler, HealthChecker |
+| `pkg/channels/wecom/` | `"wecom_app"` | MediaSender, WebhookHandler, HealthChecker |
| `pkg/channels/qq/` | `"qq"` | — |
-| `pkg/channels/whatsapp/` | `"whatsapp"` | — |
+| `pkg/channels/whatsapp/` | `"whatsapp"` | — (Bridge 模式) |
+| `pkg/channels/whatsapp_native/` | `"whatsapp_native"` | — (原生 whatsmeow 模式) |
| `pkg/channels/maixcam/` | `"maixcam"` | — |
-| `pkg/channels/pico/` | `"pico"` | WebhookHandler (Pico Protocol), TypingCapable, PlaceholderCapable |
+| `pkg/channels/pico/` | `"pico"` | TypingCapable, PlaceholderCapable, MessageEditor, WebhookHandler |
### A.3 接口速查表
@@ -1231,6 +1274,7 @@ type Channel interface {
IsRunning() bool
IsAllowed(senderID string) bool
IsAllowedSender(sender bus.SenderInfo) bool
+ ReasoningChannelID() string
}
// ===== 可选实现 =====
@@ -1324,8 +1368,16 @@ agentLoop.Stop() // 停止 Agent
1. **媒体清理暂时禁用**:Agent loop 中的 `ReleaseAll` 调用被注释掉了(`refactor(loop): disable media cleanup to prevent premature file deletion`),因为会话边界尚未明确定义。TTL 清理仍然有效。
-2. **Feishu 架构特定编译**:Feishu channel 使用 build tags 区分 32 位和 64 位架构(`feishu_32.go` / `feishu_64.go`)。
+2. **Feishu 架构特定编译**:Feishu channel 使用 build tags 区分 32 位和 64 位架构(`feishu_32.go` / `feishu_64.go`)。Feishu 使用 SDK 的 WebSocket 模式(非 HTTP webhook),因此不实现 `WebhookHandler`。
+
+3. **WeCom 有两个工厂**:`"wecom"`(Bot 模式,纯 webhook)和 `"wecom_app"`(应用模式,支持 MediaSender)分别注册。两者都实现了 `WebhookHandler` 和 `HealthChecker`。
+
+4. **Pico Protocol**:`pkg/channels/pico/` 实现了一个自定义的 PicoClaw 原生协议 channel,通过 WebSocket webhook (`/pico/ws`) 接收消息。
+
+5. **WhatsApp 有两种模式**:`"whatsapp"`(Bridge 模式,通过外部 bridge URL 通信)和 `"whatsapp_native"`(原生 whatsmeow 模式,直接连接 WhatsApp)。Manager 根据 `WhatsAppConfig.UseNative` 决定初始化哪个。
+
+6. **DingTalk 使用 Stream 模式**:DingTalk 使用 SDK 的 Stream/WebSocket 模式(非 HTTP webhook),因此不实现 `WebhookHandler`。
-3. **WeCom 有两个工厂**:`"wecom"`(Bot 模式)和 `"wecom_app"`(应用模式)分别注册。
+7. **PlaceholderConfig 的配置与实现**:`PlaceholderConfig` 出现在 6 个 channel config 中(Telegram、Discord、Slack、LINE、OneBot、Pico),但只有实现了 `PlaceholderCapable` + `MessageEditor` 的 channel(Telegram、Discord、Pico)能真正使用占位消息编辑功能。其余 channel 的 `PlaceholderConfig` 为预留字段。
-4. **Pico Protocol**:`pkg/channels/pico/` 实现了一个自定义的 PicoClaw 原生协议 channel,通过 webhook 接收消息。
\ No newline at end of file
+8. **ReasoningChannelID**:大多数 channel config 都包含 `reasoning_channel_id` 字段,用于将 LLM 的思维链(reasoning/thinking)路由到指定 channel(WhatsApp、Telegram、Feishu、Discord、MaixCam、QQ、DingTalk、Slack、LINE、OneBot、WeCom、WeComApp)。注意:`PicoConfig` 目前不包含该字段。`BaseChannel` 通过 `WithReasoningChannelID` 选项和 `ReasoningChannelID()` 方法暴露此配置。
\ No newline at end of file