mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-02 08:47:26 +08:00
Compare commits
1 Commits
fix/plugin
...
hermes/her
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4982f0c372 |
@@ -33,6 +33,7 @@ PLATFORMS: OrderedDict[str, PlatformInfo] = OrderedDict([
|
|||||||
("dingtalk", PlatformInfo(label="💬 DingTalk", default_toolset="hermes-dingtalk")),
|
("dingtalk", PlatformInfo(label="💬 DingTalk", default_toolset="hermes-dingtalk")),
|
||||||
("feishu", PlatformInfo(label="🪽 Feishu", default_toolset="hermes-feishu")),
|
("feishu", PlatformInfo(label="🪽 Feishu", default_toolset="hermes-feishu")),
|
||||||
("wecom", PlatformInfo(label="💬 WeCom", default_toolset="hermes-wecom")),
|
("wecom", PlatformInfo(label="💬 WeCom", default_toolset="hermes-wecom")),
|
||||||
|
("wecom_callback", PlatformInfo(label="💬 WeCom Callback", default_toolset="hermes-wecom-callback")),
|
||||||
("weixin", PlatformInfo(label="💬 Weixin", default_toolset="hermes-weixin")),
|
("weixin", PlatformInfo(label="💬 Weixin", default_toolset="hermes-weixin")),
|
||||||
("webhook", PlatformInfo(label="🔗 Webhook", default_toolset="hermes-webhook")),
|
("webhook", PlatformInfo(label="🔗 Webhook", default_toolset="hermes-webhook")),
|
||||||
("api_server", PlatformInfo(label="🌐 API Server", default_toolset="hermes-api-server")),
|
("api_server", PlatformInfo(label="🌐 API Server", default_toolset="hermes-api-server")),
|
||||||
|
|||||||
@@ -2005,6 +2005,12 @@ def _setup_wecom():
|
|||||||
_gateway_setup_wecom()
|
_gateway_setup_wecom()
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_wecom_callback():
|
||||||
|
"""Configure WeCom Callback (self-built app) via gateway setup."""
|
||||||
|
from hermes_cli.gateway import _setup_wecom_callback as _gw_setup
|
||||||
|
_gw_setup()
|
||||||
|
|
||||||
|
|
||||||
def _setup_bluebubbles():
|
def _setup_bluebubbles():
|
||||||
"""Configure BlueBubbles iMessage gateway."""
|
"""Configure BlueBubbles iMessage gateway."""
|
||||||
print_header("BlueBubbles (iMessage)")
|
print_header("BlueBubbles (iMessage)")
|
||||||
@@ -2130,6 +2136,7 @@ _GATEWAY_PLATFORMS = [
|
|||||||
("DingTalk", "DINGTALK_CLIENT_ID", _setup_dingtalk),
|
("DingTalk", "DINGTALK_CLIENT_ID", _setup_dingtalk),
|
||||||
("Feishu / Lark", "FEISHU_APP_ID", _setup_feishu),
|
("Feishu / Lark", "FEISHU_APP_ID", _setup_feishu),
|
||||||
("WeCom (Enterprise WeChat)", "WECOM_BOT_ID", _setup_wecom),
|
("WeCom (Enterprise WeChat)", "WECOM_BOT_ID", _setup_wecom),
|
||||||
|
("WeCom Callback (Self-Built App)", "WECOM_CALLBACK_CORP_ID", _setup_wecom_callback),
|
||||||
("Weixin (WeChat)", "WEIXIN_ACCOUNT_ID", _setup_weixin),
|
("Weixin (WeChat)", "WEIXIN_ACCOUNT_ID", _setup_weixin),
|
||||||
("BlueBubbles (iMessage)", "BLUEBUBBLES_SERVER_URL", _setup_bluebubbles),
|
("BlueBubbles (iMessage)", "BLUEBUBBLES_SERVER_URL", _setup_bluebubbles),
|
||||||
("Webhooks (GitHub, GitLab, etc.)", "WEBHOOK_ENABLED", _setup_webhooks),
|
("Webhooks (GitHub, GitLab, etc.)", "WEBHOOK_ENABLED", _setup_webhooks),
|
||||||
|
|||||||
@@ -302,6 +302,7 @@ def show_status(args):
|
|||||||
"DingTalk": ("DINGTALK_CLIENT_ID", None),
|
"DingTalk": ("DINGTALK_CLIENT_ID", None),
|
||||||
"Feishu": ("FEISHU_APP_ID", "FEISHU_HOME_CHANNEL"),
|
"Feishu": ("FEISHU_APP_ID", "FEISHU_HOME_CHANNEL"),
|
||||||
"WeCom": ("WECOM_BOT_ID", "WECOM_HOME_CHANNEL"),
|
"WeCom": ("WECOM_BOT_ID", "WECOM_HOME_CHANNEL"),
|
||||||
|
"WeCom Callback": ("WECOM_CALLBACK_CORP_ID", None),
|
||||||
"Weixin": ("WEIXIN_ACCOUNT_ID", "WEIXIN_HOME_CHANNEL"),
|
"Weixin": ("WEIXIN_ACCOUNT_ID", "WEIXIN_HOME_CHANNEL"),
|
||||||
"BlueBubbles": ("BLUEBUBBLES_SERVER_URL", "BLUEBUBBLES_HOME_CHANNEL"),
|
"BlueBubbles": ("BLUEBUBBLES_SERVER_URL", "BLUEBUBBLES_HOME_CHANNEL"),
|
||||||
}
|
}
|
||||||
|
|||||||
256
website/docs/developer-guide/adding-platform-adapters.md
Normal file
256
website/docs/developer-guide/adding-platform-adapters.md
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 9
|
||||||
|
---
|
||||||
|
|
||||||
|
# Adding a Platform Adapter
|
||||||
|
|
||||||
|
This guide covers adding a new messaging platform to the Hermes gateway. A platform adapter connects Hermes to an external messaging service (Telegram, Discord, WeCom, etc.) so users can interact with the agent through that service.
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
Adding a platform adapter touches 20+ files across code, config, and docs. Use this guide as a checklist — the adapter file itself is typically only 40% of the work.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
User ↔ Messaging Platform ↔ Platform Adapter ↔ Gateway Runner ↔ AIAgent
|
||||||
|
```
|
||||||
|
|
||||||
|
Every adapter extends `BasePlatformAdapter` from `gateway/platforms/base.py` and implements:
|
||||||
|
|
||||||
|
- **`connect()`** — Establish connection (WebSocket, long-poll, HTTP server, etc.)
|
||||||
|
- **`disconnect()`** — Clean shutdown
|
||||||
|
- **`send()`** — Send a text message to a chat
|
||||||
|
- **`send_typing()`** — Show typing indicator (optional)
|
||||||
|
- **`get_chat_info()`** — Return chat metadata
|
||||||
|
|
||||||
|
Inbound messages are received by the adapter and forwarded via `self.handle_message(event)`, which the base class routes to the gateway runner.
|
||||||
|
|
||||||
|
## Step-by-Step Checklist
|
||||||
|
|
||||||
|
### 1. Platform Enum
|
||||||
|
|
||||||
|
Add your platform to the `Platform` enum in `gateway/config.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
class Platform(str, Enum):
|
||||||
|
# ... existing platforms ...
|
||||||
|
NEWPLAT = "newplat"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Adapter File
|
||||||
|
|
||||||
|
Create `gateway/platforms/newplat.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from gateway.config import Platform, PlatformConfig
|
||||||
|
from gateway.platforms.base import (
|
||||||
|
BasePlatformAdapter, MessageEvent, MessageType, SendResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
def check_newplat_requirements() -> bool:
|
||||||
|
"""Return True if dependencies are available."""
|
||||||
|
return SOME_SDK_AVAILABLE
|
||||||
|
|
||||||
|
class NewPlatAdapter(BasePlatformAdapter):
|
||||||
|
def __init__(self, config: PlatformConfig):
|
||||||
|
super().__init__(config, Platform.NEWPLAT)
|
||||||
|
# Read config from config.extra dict
|
||||||
|
extra = config.extra or {}
|
||||||
|
self._api_key = extra.get("api_key") or os.getenv("NEWPLAT_API_KEY", "")
|
||||||
|
|
||||||
|
async def connect(self) -> bool:
|
||||||
|
# Set up connection, start polling/webhook
|
||||||
|
self._mark_connected()
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def disconnect(self) -> None:
|
||||||
|
self._running = False
|
||||||
|
self._mark_disconnected()
|
||||||
|
|
||||||
|
async def send(self, chat_id, content, reply_to=None, metadata=None):
|
||||||
|
# Send message via platform API
|
||||||
|
return SendResult(success=True, message_id="...")
|
||||||
|
|
||||||
|
async def get_chat_info(self, chat_id):
|
||||||
|
return {"name": chat_id, "type": "dm"}
|
||||||
|
```
|
||||||
|
|
||||||
|
For inbound messages, build a `MessageEvent` and call `self.handle_message(event)`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
source = self.build_source(
|
||||||
|
chat_id=chat_id,
|
||||||
|
chat_name=name,
|
||||||
|
chat_type="dm", # or "group"
|
||||||
|
user_id=user_id,
|
||||||
|
user_name=user_name,
|
||||||
|
)
|
||||||
|
event = MessageEvent(
|
||||||
|
text=content,
|
||||||
|
message_type=MessageType.TEXT,
|
||||||
|
source=source,
|
||||||
|
message_id=msg_id,
|
||||||
|
)
|
||||||
|
await self.handle_message(event)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Gateway Config (`gateway/config.py`)
|
||||||
|
|
||||||
|
Three touchpoints:
|
||||||
|
|
||||||
|
1. **`get_connected_platforms()`** — Add a check for your platform's required credentials
|
||||||
|
2. **`load_gateway_config()`** — Add token env map entry: `Platform.NEWPLAT: "NEWPLAT_TOKEN"`
|
||||||
|
3. **`_apply_env_overrides()`** — Map all `NEWPLAT_*` env vars to config
|
||||||
|
|
||||||
|
### 4. Gateway Runner (`gateway/run.py`)
|
||||||
|
|
||||||
|
Five touchpoints:
|
||||||
|
|
||||||
|
1. **`_create_adapter()`** — Add an `elif platform == Platform.NEWPLAT:` branch
|
||||||
|
2. **`_is_user_authorized()` allowed_users map** — `Platform.NEWPLAT: "NEWPLAT_ALLOWED_USERS"`
|
||||||
|
3. **`_is_user_authorized()` allow_all map** — `Platform.NEWPLAT: "NEWPLAT_ALLOW_ALL_USERS"`
|
||||||
|
4. **Early env check `_any_allowlist` tuple** — Add `"NEWPLAT_ALLOWED_USERS"`
|
||||||
|
5. **Early env check `_allow_all` tuple** — Add `"NEWPLAT_ALLOW_ALL_USERS"`
|
||||||
|
6. **`_UPDATE_ALLOWED_PLATFORMS` frozenset** — Add `Platform.NEWPLAT`
|
||||||
|
|
||||||
|
### 5. Cross-Platform Delivery
|
||||||
|
|
||||||
|
1. **`gateway/platforms/webhook.py`** — Add `"newplat"` to the delivery type tuple
|
||||||
|
2. **`cron/scheduler.py`** — Add to `_KNOWN_DELIVERY_PLATFORMS` frozenset and `_deliver_result()` platform map
|
||||||
|
|
||||||
|
### 6. CLI Integration
|
||||||
|
|
||||||
|
1. **`hermes_cli/config.py`** — Add all `NEWPLAT_*` vars to `_EXTRA_ENV_KEYS`
|
||||||
|
2. **`hermes_cli/gateway.py`** — Add entry to `_PLATFORMS` list with key, label, emoji, token_var, setup_instructions, and vars
|
||||||
|
3. **`hermes_cli/platforms.py`** — Add `PlatformInfo` entry with label and default_toolset (used by `skills_config` and `tools_config` TUIs)
|
||||||
|
4. **`hermes_cli/setup.py`** — Add `_setup_newplat()` function (can delegate to `gateway.py`) and add tuple to the messaging platforms list
|
||||||
|
5. **`hermes_cli/status.py`** — Add platform detection entry: `"NewPlat": ("NEWPLAT_TOKEN", "NEWPLAT_HOME_CHANNEL")`
|
||||||
|
6. **`hermes_cli/dump.py`** — Add `"newplat": "NEWPLAT_TOKEN"` to platform detection dict
|
||||||
|
|
||||||
|
### 7. Tools
|
||||||
|
|
||||||
|
1. **`tools/send_message_tool.py`** — Add `"newplat": Platform.NEWPLAT` to platform map
|
||||||
|
2. **`tools/cronjob_tools.py`** — Add `newplat` to the delivery target description string
|
||||||
|
|
||||||
|
### 8. Toolsets
|
||||||
|
|
||||||
|
1. **`toolsets.py`** — Add `"hermes-newplat"` toolset definition with `_HERMES_CORE_TOOLS`
|
||||||
|
2. **`toolsets.py`** — Add `"hermes-newplat"` to the `"hermes-gateway"` includes list
|
||||||
|
|
||||||
|
### 9. Optional: Platform Hints
|
||||||
|
|
||||||
|
**`agent/prompt_builder.py`** — If your platform has specific rendering limitations (no markdown, message length limits, etc.), add an entry to the `_PLATFORM_HINTS` dict. This injects platform-specific guidance into the system prompt:
|
||||||
|
|
||||||
|
```python
|
||||||
|
_PLATFORM_HINTS = {
|
||||||
|
# ...
|
||||||
|
"newplat": (
|
||||||
|
"You are chatting via NewPlat. It supports markdown formatting "
|
||||||
|
"but has a 4000-character message limit."
|
||||||
|
),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Not all platforms need hints — only add one if the agent's behavior should differ.
|
||||||
|
|
||||||
|
### 10. Tests
|
||||||
|
|
||||||
|
Create `tests/gateway/test_newplat.py` covering:
|
||||||
|
|
||||||
|
- Adapter construction from config
|
||||||
|
- Message event building
|
||||||
|
- Send method (mock the external API)
|
||||||
|
- Platform-specific features (encryption, routing, etc.)
|
||||||
|
|
||||||
|
### 11. Documentation
|
||||||
|
|
||||||
|
| File | What to add |
|
||||||
|
|------|-------------|
|
||||||
|
| `website/docs/user-guide/messaging/newplat.md` | Full platform setup page |
|
||||||
|
| `website/docs/user-guide/messaging/index.md` | Platform comparison table, architecture diagram, toolsets table, security section, next-steps link |
|
||||||
|
| `website/docs/reference/environment-variables.md` | All NEWPLAT_* env vars |
|
||||||
|
| `website/docs/reference/toolsets-reference.md` | hermes-newplat toolset |
|
||||||
|
| `website/docs/integrations/index.md` | Platform link |
|
||||||
|
| `website/sidebars.ts` | Sidebar entry for the docs page |
|
||||||
|
| `website/docs/developer-guide/architecture.md` | Adapter count + listing |
|
||||||
|
| `website/docs/developer-guide/gateway-internals.md` | Adapter file listing |
|
||||||
|
|
||||||
|
## Parity Audit
|
||||||
|
|
||||||
|
Before marking a new platform PR as complete, run a parity audit against an established platform:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find every .py file mentioning the reference platform
|
||||||
|
search_files "bluebubbles" output_mode="files_only" file_glob="*.py"
|
||||||
|
|
||||||
|
# Find every .py file mentioning the new platform
|
||||||
|
search_files "newplat" output_mode="files_only" file_glob="*.py"
|
||||||
|
|
||||||
|
# Any file in the first set but not the second is a potential gap
|
||||||
|
```
|
||||||
|
|
||||||
|
Repeat for `.md` and `.ts` files. Investigate each gap — is it a platform enumeration (needs updating) or a platform-specific reference (skip)?
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Long-Poll Adapters
|
||||||
|
|
||||||
|
If your adapter uses long-polling (like Telegram or Weixin), use a polling loop task:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def connect(self):
|
||||||
|
self._poll_task = asyncio.create_task(self._poll_loop())
|
||||||
|
self._mark_connected()
|
||||||
|
|
||||||
|
async def _poll_loop(self):
|
||||||
|
while self._running:
|
||||||
|
messages = await self._fetch_updates()
|
||||||
|
for msg in messages:
|
||||||
|
await self.handle_message(self._build_event(msg))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Callback/Webhook Adapters
|
||||||
|
|
||||||
|
If the platform pushes messages to your endpoint (like WeCom Callback), run an HTTP server:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def connect(self):
|
||||||
|
self._app = web.Application()
|
||||||
|
self._app.router.add_post("/callback", self._handle_callback)
|
||||||
|
# ... start aiohttp server
|
||||||
|
self._mark_connected()
|
||||||
|
|
||||||
|
async def _handle_callback(self, request):
|
||||||
|
event = self._build_event(await request.text())
|
||||||
|
await self._message_queue.put(event)
|
||||||
|
return web.Response(text="success") # Acknowledge immediately
|
||||||
|
```
|
||||||
|
|
||||||
|
For platforms with tight response deadlines (e.g., WeCom's 5-second limit), always acknowledge immediately and deliver the agent's reply proactively via API later. Agent sessions run 3–30 minutes — inline replies within a callback response window are not feasible.
|
||||||
|
|
||||||
|
### Token Locks
|
||||||
|
|
||||||
|
If the adapter holds a persistent connection with a unique credential, add a scoped lock to prevent two profiles from using the same credential:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from gateway.status import acquire_scoped_lock, release_scoped_lock
|
||||||
|
|
||||||
|
async def connect(self):
|
||||||
|
if not acquire_scoped_lock("newplat", self._token):
|
||||||
|
logger.error("Token already in use by another profile")
|
||||||
|
return False
|
||||||
|
# ... connect
|
||||||
|
|
||||||
|
async def disconnect(self):
|
||||||
|
release_scoped_lock("newplat", self._token)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reference Implementations
|
||||||
|
|
||||||
|
| Adapter | Pattern | Complexity | Good reference for |
|
||||||
|
|---------|---------|------------|-------------------|
|
||||||
|
| `bluebubbles.py` | REST + webhook | Medium | Simple REST API integration |
|
||||||
|
| `weixin.py` | Long-poll + CDN | High | Media handling, encryption |
|
||||||
|
| `wecom_callback.py` | Callback/webhook | Medium | HTTP server, AES crypto, multi-app |
|
||||||
|
| `telegram.py` | Long-poll + Bot API | High | Full-featured adapter with groups, threads |
|
||||||
@@ -82,7 +82,7 @@ Speech-to-text supports three providers: local Whisper (free, runs on-device), G
|
|||||||
|
|
||||||
Hermes runs as a gateway bot on 15+ messaging platforms, all configured through the same `gateway` subsystem:
|
Hermes runs as a gateway bot on 15+ messaging platforms, all configured through the same `gateway` subsystem:
|
||||||
|
|
||||||
- **[Telegram](/docs/user-guide/messaging/telegram)**, **[Discord](/docs/user-guide/messaging/discord)**, **[Slack](/docs/user-guide/messaging/slack)**, **[WhatsApp](/docs/user-guide/messaging/whatsapp)**, **[Signal](/docs/user-guide/messaging/signal)**, **[Matrix](/docs/user-guide/messaging/matrix)**, **[Mattermost](/docs/user-guide/messaging/mattermost)**, **[Email](/docs/user-guide/messaging/email)**, **[SMS](/docs/user-guide/messaging/sms)**, **[DingTalk](/docs/user-guide/messaging/dingtalk)**, **[Feishu/Lark](/docs/user-guide/messaging/feishu)**, **[WeCom](/docs/user-guide/messaging/wecom)**, **[Weixin](/docs/user-guide/messaging/weixin)**, **[BlueBubbles](/docs/user-guide/messaging/bluebubbles)**, **[Home Assistant](/docs/user-guide/messaging/homeassistant)**, **[Webhooks](/docs/user-guide/messaging/webhooks)**
|
- **[Telegram](/docs/user-guide/messaging/telegram)**, **[Discord](/docs/user-guide/messaging/discord)**, **[Slack](/docs/user-guide/messaging/slack)**, **[WhatsApp](/docs/user-guide/messaging/whatsapp)**, **[Signal](/docs/user-guide/messaging/signal)**, **[Matrix](/docs/user-guide/messaging/matrix)**, **[Mattermost](/docs/user-guide/messaging/mattermost)**, **[Email](/docs/user-guide/messaging/email)**, **[SMS](/docs/user-guide/messaging/sms)**, **[DingTalk](/docs/user-guide/messaging/dingtalk)**, **[Feishu/Lark](/docs/user-guide/messaging/feishu)**, **[WeCom](/docs/user-guide/messaging/wecom)**, **[WeCom Callback](/docs/user-guide/messaging/wecom-callback)**, **[Weixin](/docs/user-guide/messaging/weixin)**, **[BlueBubbles](/docs/user-guide/messaging/bluebubbles)**, **[Home Assistant](/docs/user-guide/messaging/homeassistant)**, **[Webhooks](/docs/user-guide/messaging/webhooks)**
|
||||||
|
|
||||||
See the [Messaging Gateway overview](/docs/user-guide/messaging) for the platform comparison table and setup guide.
|
See the [Messaging Gateway overview](/docs/user-guide/messaging) for the platform comparison table and setup guide.
|
||||||
|
|
||||||
|
|||||||
@@ -232,6 +232,15 @@ For cloud sandbox backends, persistence is filesystem-oriented. `TERMINAL_LIFETI
|
|||||||
| `WECOM_WEBSOCKET_URL` | Custom WebSocket URL (default: `wss://openws.work.weixin.qq.com`) |
|
| `WECOM_WEBSOCKET_URL` | Custom WebSocket URL (default: `wss://openws.work.weixin.qq.com`) |
|
||||||
| `WECOM_ALLOWED_USERS` | Comma-separated WeCom user IDs allowed to message the bot |
|
| `WECOM_ALLOWED_USERS` | Comma-separated WeCom user IDs allowed to message the bot |
|
||||||
| `WECOM_HOME_CHANNEL` | WeCom chat ID for cron delivery and notifications |
|
| `WECOM_HOME_CHANNEL` | WeCom chat ID for cron delivery and notifications |
|
||||||
|
| `WECOM_CALLBACK_CORP_ID` | WeCom enterprise Corp ID for callback self-built app |
|
||||||
|
| `WECOM_CALLBACK_CORP_SECRET` | Corp secret for the self-built app |
|
||||||
|
| `WECOM_CALLBACK_AGENT_ID` | Agent ID of the self-built app |
|
||||||
|
| `WECOM_CALLBACK_TOKEN` | Callback verification token |
|
||||||
|
| `WECOM_CALLBACK_ENCODING_AES_KEY` | AES key for callback encryption |
|
||||||
|
| `WECOM_CALLBACK_HOST` | Callback server bind address (default: `0.0.0.0`) |
|
||||||
|
| `WECOM_CALLBACK_PORT` | Callback server port (default: `8645`) |
|
||||||
|
| `WECOM_CALLBACK_ALLOWED_USERS` | Comma-separated user IDs for allowlist |
|
||||||
|
| `WECOM_CALLBACK_ALLOW_ALL_USERS` | Set `true` to allow all users without an allowlist |
|
||||||
| `WEIXIN_ACCOUNT_ID` | Weixin account ID obtained via QR login through iLink Bot API |
|
| `WEIXIN_ACCOUNT_ID` | Weixin account ID obtained via QR login through iLink Bot API |
|
||||||
| `WEIXIN_TOKEN` | Weixin authentication token obtained via QR login through iLink Bot API |
|
| `WEIXIN_TOKEN` | Weixin authentication token obtained via QR login through iLink Bot API |
|
||||||
| `WEIXIN_BASE_URL` | Override Weixin iLink Bot API base URL (default: `https://ilinkai.weixin.qq.com`) |
|
| `WEIXIN_BASE_URL` | Override Weixin iLink Bot API base URL (default: `https://ilinkai.weixin.qq.com`) |
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ Platform toolsets define the complete tool configuration for a deployment target
|
|||||||
| `hermes-dingtalk` | Same as `hermes-cli`. |
|
| `hermes-dingtalk` | Same as `hermes-cli`. |
|
||||||
| `hermes-feishu` | Same as `hermes-cli`. |
|
| `hermes-feishu` | Same as `hermes-cli`. |
|
||||||
| `hermes-wecom` | Same as `hermes-cli`. |
|
| `hermes-wecom` | Same as `hermes-cli`. |
|
||||||
|
| `hermes-wecom-callback` | WeCom callback toolset — enterprise self-built app messaging (full access). |
|
||||||
| `hermes-weixin` | Same as `hermes-cli`. |
|
| `hermes-weixin` | Same as `hermes-cli`. |
|
||||||
| `hermes-bluebubbles` | Same as `hermes-cli`. |
|
| `hermes-bluebubbles` | Same as `hermes-cli`. |
|
||||||
| `hermes-homeassistant` | Same as `hermes-cli`. |
|
| `hermes-homeassistant` | Same as `hermes-cli`. |
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ For the full voice feature set — including CLI microphone mode, spoken replies
|
|||||||
| DingTalk | — | — | — | — | — | ✅ | ✅ |
|
| DingTalk | — | — | — | — | — | ✅ | ✅ |
|
||||||
| Feishu/Lark | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
| Feishu/Lark | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||||
| WeCom | ✅ | ✅ | ✅ | — | — | ✅ | ✅ |
|
| WeCom | ✅ | ✅ | ✅ | — | — | ✅ | ✅ |
|
||||||
|
| WeCom Callback | — | — | — | — | — | — | — |
|
||||||
| Weixin | ✅ | ✅ | ✅ | — | — | ✅ | ✅ |
|
| Weixin | ✅ | ✅ | ✅ | — | — | ✅ | ✅ |
|
||||||
| BlueBubbles | — | ✅ | ✅ | — | ✅ | ✅ | — |
|
| BlueBubbles | — | ✅ | ✅ | — | ✅ | ✅ | — |
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ flowchart TB
|
|||||||
dt[DingTalk]
|
dt[DingTalk]
|
||||||
fs[Feishu/Lark]
|
fs[Feishu/Lark]
|
||||||
wc[WeCom]
|
wc[WeCom]
|
||||||
|
wcb[WeCom Callback]
|
||||||
wx[Weixin]
|
wx[Weixin]
|
||||||
bb[BlueBubbles]
|
bb[BlueBubbles]
|
||||||
api["API Server<br/>(OpenAI-compatible)"]
|
api["API Server<br/>(OpenAI-compatible)"]
|
||||||
@@ -75,6 +77,7 @@ flowchart TB
|
|||||||
dt --> store
|
dt --> store
|
||||||
fs --> store
|
fs --> store
|
||||||
wc --> store
|
wc --> store
|
||||||
|
wcb --> store
|
||||||
wx --> store
|
wx --> store
|
||||||
bb --> store
|
bb --> store
|
||||||
api --> store
|
api --> store
|
||||||
@@ -180,6 +183,7 @@ MATRIX_ALLOWED_USERS=@alice:matrix.org
|
|||||||
DINGTALK_ALLOWED_USERS=user-id-1
|
DINGTALK_ALLOWED_USERS=user-id-1
|
||||||
FEISHU_ALLOWED_USERS=ou_xxxxxxxx,ou_yyyyyyyy
|
FEISHU_ALLOWED_USERS=ou_xxxxxxxx,ou_yyyyyyyy
|
||||||
WECOM_ALLOWED_USERS=user-id-1,user-id-2
|
WECOM_ALLOWED_USERS=user-id-1,user-id-2
|
||||||
|
WECOM_CALLBACK_ALLOWED_USERS=user-id-1,user-id-2
|
||||||
|
|
||||||
# Or allow
|
# Or allow
|
||||||
GATEWAY_ALLOWED_USERS=123456789,987654321
|
GATEWAY_ALLOWED_USERS=123456789,987654321
|
||||||
@@ -362,6 +366,7 @@ Each platform has its own toolset:
|
|||||||
| DingTalk | `hermes-dingtalk` | Full tools including terminal |
|
| DingTalk | `hermes-dingtalk` | Full tools including terminal |
|
||||||
| Feishu/Lark | `hermes-feishu` | Full tools including terminal |
|
| Feishu/Lark | `hermes-feishu` | Full tools including terminal |
|
||||||
| WeCom | `hermes-wecom` | Full tools including terminal |
|
| WeCom | `hermes-wecom` | Full tools including terminal |
|
||||||
|
| WeCom Callback | `hermes-wecom-callback` | Full tools including terminal |
|
||||||
| Weixin | `hermes-weixin` | Full tools including terminal |
|
| Weixin | `hermes-weixin` | Full tools including terminal |
|
||||||
| BlueBubbles | `hermes-bluebubbles` | Full tools including terminal |
|
| BlueBubbles | `hermes-bluebubbles` | Full tools including terminal |
|
||||||
| API Server | `hermes` (default) | Full tools including terminal |
|
| API Server | `hermes` (default) | Full tools including terminal |
|
||||||
@@ -382,6 +387,7 @@ Each platform has its own toolset:
|
|||||||
- [DingTalk Setup](dingtalk.md)
|
- [DingTalk Setup](dingtalk.md)
|
||||||
- [Feishu/Lark Setup](feishu.md)
|
- [Feishu/Lark Setup](feishu.md)
|
||||||
- [WeCom Setup](wecom.md)
|
- [WeCom Setup](wecom.md)
|
||||||
|
- [WeCom Callback Setup](wecom-callback.md)
|
||||||
- [Weixin Setup (WeChat)](weixin.md)
|
- [Weixin Setup (WeChat)](weixin.md)
|
||||||
- [BlueBubbles Setup (iMessage)](bluebubbles.md)
|
- [BlueBubbles Setup (iMessage)](bluebubbles.md)
|
||||||
- [Open WebUI + API Server](open-webui.md)
|
- [Open WebUI + API Server](open-webui.md)
|
||||||
|
|||||||
147
website/docs/user-guide/messaging/wecom-callback.md
Normal file
147
website/docs/user-guide/messaging/wecom-callback.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
---
|
||||||
|
sidebar_position: 15
|
||||||
|
---
|
||||||
|
|
||||||
|
# WeCom Callback (Self-Built App)
|
||||||
|
|
||||||
|
Connect Hermes to WeCom (Enterprise WeChat) as a self-built enterprise application using the callback/webhook model.
|
||||||
|
|
||||||
|
:::info WeCom Bot vs WeCom Callback
|
||||||
|
Hermes supports two WeCom integration modes:
|
||||||
|
- **[WeCom Bot](wecom.md)** — bot-style, connects via WebSocket. Simpler setup, works in group chats.
|
||||||
|
- **WeCom Callback** (this page) — self-built app, receives encrypted XML callbacks. Shows as a first-class app in users' WeCom sidebar. Supports multi-corp routing.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. You register a self-built application in the WeCom Admin Console
|
||||||
|
2. WeCom pushes encrypted XML to your HTTP callback endpoint
|
||||||
|
3. Hermes decrypts the message, queues it for the agent
|
||||||
|
4. Immediately acknowledges (silent — nothing displayed to the user)
|
||||||
|
5. The agent processes the request (typically 3–30 minutes)
|
||||||
|
6. The reply is delivered proactively via the WeCom `message/send` API
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- A WeCom enterprise account with admin access
|
||||||
|
- `aiohttp` and `httpx` Python packages (included in the default install)
|
||||||
|
- A publicly reachable server for the callback URL (or a tunnel like ngrok)
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Create a Self-Built App in WeCom
|
||||||
|
|
||||||
|
1. Go to [WeCom Admin Console](https://work.weixin.qq.com/) → **Applications** → **Create App**
|
||||||
|
2. Note your **Corp ID** (shown at the top of the admin console)
|
||||||
|
3. In the app settings, create a **Corp Secret**
|
||||||
|
4. Note the **Agent ID** from the app's overview page
|
||||||
|
5. Under **Receive Messages**, configure the callback URL:
|
||||||
|
- URL: `http://YOUR_PUBLIC_IP:8645/wecom/callback`
|
||||||
|
- Token: Generate a random token (WeCom provides one)
|
||||||
|
- EncodingAESKey: Generate a key (WeCom provides one)
|
||||||
|
|
||||||
|
### 2. Configure Environment Variables
|
||||||
|
|
||||||
|
Add to your `.env` file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
WECOM_CALLBACK_CORP_ID=your-corp-id
|
||||||
|
WECOM_CALLBACK_CORP_SECRET=your-corp-secret
|
||||||
|
WECOM_CALLBACK_AGENT_ID=1000002
|
||||||
|
WECOM_CALLBACK_TOKEN=your-callback-token
|
||||||
|
WECOM_CALLBACK_ENCODING_AES_KEY=your-43-char-aes-key
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
WECOM_CALLBACK_HOST=0.0.0.0
|
||||||
|
WECOM_CALLBACK_PORT=8645
|
||||||
|
WECOM_CALLBACK_ALLOWED_USERS=user1,user2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Start the Gateway
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hermes gateway start
|
||||||
|
```
|
||||||
|
|
||||||
|
The callback adapter starts an HTTP server on the configured port. WeCom will verify the callback URL via a GET request, then begin sending messages via POST.
|
||||||
|
|
||||||
|
## Configuration Reference
|
||||||
|
|
||||||
|
Set these in `config.yaml` under `platforms.wecom_callback.extra`, or use environment variables:
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `corp_id` | — | WeCom enterprise Corp ID (required) |
|
||||||
|
| `corp_secret` | — | Corp secret for the self-built app (required) |
|
||||||
|
| `agent_id` | — | Agent ID of the self-built app (required) |
|
||||||
|
| `token` | — | Callback verification token (required) |
|
||||||
|
| `encoding_aes_key` | — | 43-character AES key for callback encryption (required) |
|
||||||
|
| `host` | `0.0.0.0` | Bind address for the HTTP callback server |
|
||||||
|
| `port` | `8645` | Port for the HTTP callback server |
|
||||||
|
| `path` | `/wecom/callback` | URL path for the callback endpoint |
|
||||||
|
|
||||||
|
## Multi-App Routing
|
||||||
|
|
||||||
|
For enterprises running multiple self-built apps (e.g., across different departments or subsidiaries), configure the `apps` list in `config.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
platforms:
|
||||||
|
wecom_callback:
|
||||||
|
enabled: true
|
||||||
|
extra:
|
||||||
|
host: "0.0.0.0"
|
||||||
|
port: 8645
|
||||||
|
apps:
|
||||||
|
- name: "dept-a"
|
||||||
|
corp_id: "ww_corp_a"
|
||||||
|
corp_secret: "secret-a"
|
||||||
|
agent_id: "1000002"
|
||||||
|
token: "token-a"
|
||||||
|
encoding_aes_key: "key-a-43-chars..."
|
||||||
|
- name: "dept-b"
|
||||||
|
corp_id: "ww_corp_b"
|
||||||
|
corp_secret: "secret-b"
|
||||||
|
agent_id: "1000003"
|
||||||
|
token: "token-b"
|
||||||
|
encoding_aes_key: "key-b-43-chars..."
|
||||||
|
```
|
||||||
|
|
||||||
|
Users are scoped by `corp_id:user_id` to prevent cross-corp collisions. When a user sends a message, the adapter records which app (corp) they belong to and routes replies through the correct app's access token.
|
||||||
|
|
||||||
|
## Access Control
|
||||||
|
|
||||||
|
Restrict which users can interact with the app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Allowlist specific users
|
||||||
|
WECOM_CALLBACK_ALLOWED_USERS=zhangsan,lisi,wangwu
|
||||||
|
|
||||||
|
# Or allow all users
|
||||||
|
WECOM_CALLBACK_ALLOW_ALL_USERS=true
|
||||||
|
```
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
The adapter exposes:
|
||||||
|
|
||||||
|
| Method | Path | Purpose |
|
||||||
|
|--------|------|---------|
|
||||||
|
| GET | `/wecom/callback` | URL verification handshake (WeCom sends this during setup) |
|
||||||
|
| POST | `/wecom/callback` | Encrypted message callback (WeCom sends user messages here) |
|
||||||
|
| GET | `/health` | Health check — returns `{"status": "ok"}` |
|
||||||
|
|
||||||
|
## Encryption
|
||||||
|
|
||||||
|
All callback payloads are encrypted with AES-CBC using the EncodingAESKey. The adapter handles:
|
||||||
|
|
||||||
|
- **Inbound**: Decrypt XML payload, verify SHA1 signature
|
||||||
|
- **Outbound**: Replies sent via proactive API (not encrypted callback response)
|
||||||
|
|
||||||
|
The crypto implementation is compatible with Tencent's official WXBizMsgCrypt SDK.
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- **No streaming** — replies arrive as complete messages after the agent finishes
|
||||||
|
- **No typing indicators** — the callback model doesn't support typing status
|
||||||
|
- **Text only** — currently supports text messages; image/file/voice not yet implemented
|
||||||
|
- **Response latency** — agent sessions take 3–30 minutes; users see the reply when processing completes
|
||||||
@@ -108,6 +108,7 @@ const sidebars: SidebarsConfig = {
|
|||||||
'user-guide/messaging/dingtalk',
|
'user-guide/messaging/dingtalk',
|
||||||
'user-guide/messaging/feishu',
|
'user-guide/messaging/feishu',
|
||||||
'user-guide/messaging/wecom',
|
'user-guide/messaging/wecom',
|
||||||
|
'user-guide/messaging/wecom-callback',
|
||||||
'user-guide/messaging/weixin',
|
'user-guide/messaging/weixin',
|
||||||
'user-guide/messaging/bluebubbles',
|
'user-guide/messaging/bluebubbles',
|
||||||
'user-guide/messaging/open-webui',
|
'user-guide/messaging/open-webui',
|
||||||
@@ -175,6 +176,7 @@ const sidebars: SidebarsConfig = {
|
|||||||
items: [
|
items: [
|
||||||
'developer-guide/adding-tools',
|
'developer-guide/adding-tools',
|
||||||
'developer-guide/adding-providers',
|
'developer-guide/adding-providers',
|
||||||
|
'developer-guide/adding-platform-adapters',
|
||||||
'developer-guide/memory-provider-plugin',
|
'developer-guide/memory-provider-plugin',
|
||||||
'developer-guide/context-engine-plugin',
|
'developer-guide/context-engine-plugin',
|
||||||
'developer-guide/creating-skills',
|
'developer-guide/creating-skills',
|
||||||
|
|||||||
Reference in New Issue
Block a user