diff --git a/hermes_cli/platforms.py b/hermes_cli/platforms.py index 18307912b..df47ed095 100644 --- a/hermes_cli/platforms.py +++ b/hermes_cli/platforms.py @@ -33,6 +33,7 @@ PLATFORMS: OrderedDict[str, PlatformInfo] = OrderedDict([ ("dingtalk", PlatformInfo(label="💬 DingTalk", default_toolset="hermes-dingtalk")), ("feishu", PlatformInfo(label="🪽 Feishu", default_toolset="hermes-feishu")), ("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")), ("webhook", PlatformInfo(label="🔗 Webhook", default_toolset="hermes-webhook")), ("api_server", PlatformInfo(label="🌐 API Server", default_toolset="hermes-api-server")), diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index a25ce8491..e12f7d1a7 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -2005,6 +2005,12 @@ def _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(): """Configure BlueBubbles iMessage gateway.""" print_header("BlueBubbles (iMessage)") @@ -2130,6 +2136,7 @@ _GATEWAY_PLATFORMS = [ ("DingTalk", "DINGTALK_CLIENT_ID", _setup_dingtalk), ("Feishu / Lark", "FEISHU_APP_ID", _setup_feishu), ("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), ("BlueBubbles (iMessage)", "BLUEBUBBLES_SERVER_URL", _setup_bluebubbles), ("Webhooks (GitHub, GitLab, etc.)", "WEBHOOK_ENABLED", _setup_webhooks), diff --git a/hermes_cli/status.py b/hermes_cli/status.py index 7a7a9c645..c48c0008b 100644 --- a/hermes_cli/status.py +++ b/hermes_cli/status.py @@ -302,6 +302,7 @@ def show_status(args): "DingTalk": ("DINGTALK_CLIENT_ID", None), "Feishu": ("FEISHU_APP_ID", "FEISHU_HOME_CHANNEL"), "WeCom": ("WECOM_BOT_ID", "WECOM_HOME_CHANNEL"), + "WeCom Callback": ("WECOM_CALLBACK_CORP_ID", None), "Weixin": ("WEIXIN_ACCOUNT_ID", "WEIXIN_HOME_CHANNEL"), "BlueBubbles": ("BLUEBUBBLES_SERVER_URL", "BLUEBUBBLES_HOME_CHANNEL"), } diff --git a/website/docs/developer-guide/adding-platform-adapters.md b/website/docs/developer-guide/adding-platform-adapters.md new file mode 100644 index 000000000..1ddb07f08 --- /dev/null +++ b/website/docs/developer-guide/adding-platform-adapters.md @@ -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 | diff --git a/website/docs/integrations/index.md b/website/docs/integrations/index.md index 6dccc44e9..cfc82d41d 100644 --- a/website/docs/integrations/index.md +++ b/website/docs/integrations/index.md @@ -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: -- **[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. diff --git a/website/docs/reference/environment-variables.md b/website/docs/reference/environment-variables.md index a548a6ff6..bec5ff1c3 100644 --- a/website/docs/reference/environment-variables.md +++ b/website/docs/reference/environment-variables.md @@ -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_ALLOWED_USERS` | Comma-separated WeCom user IDs allowed to message the bot | | `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_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`) | diff --git a/website/docs/reference/toolsets-reference.md b/website/docs/reference/toolsets-reference.md index 5516cfdfa..96856552e 100644 --- a/website/docs/reference/toolsets-reference.md +++ b/website/docs/reference/toolsets-reference.md @@ -103,6 +103,7 @@ Platform toolsets define the complete tool configuration for a deployment target | `hermes-dingtalk` | Same as `hermes-cli`. | | `hermes-feishu` | 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-bluebubbles` | Same as `hermes-cli`. | | `hermes-homeassistant` | Same as `hermes-cli`. | diff --git a/website/docs/user-guide/messaging/index.md b/website/docs/user-guide/messaging/index.md index 41b031437..f4131385e 100644 --- a/website/docs/user-guide/messaging/index.md +++ b/website/docs/user-guide/messaging/index.md @@ -27,6 +27,7 @@ For the full voice feature set — including CLI microphone mode, spoken replies | DingTalk | — | — | — | — | — | ✅ | ✅ | | Feishu/Lark | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | WeCom | ✅ | ✅ | ✅ | — | — | ✅ | ✅ | +| WeCom Callback | — | — | — | — | — | — | — | | Weixin | ✅ | ✅ | ✅ | — | — | ✅ | ✅ | | BlueBubbles | — | ✅ | ✅ | — | ✅ | ✅ | — | @@ -51,6 +52,7 @@ flowchart TB dt[DingTalk] fs[Feishu/Lark] wc[WeCom] + wcb[WeCom Callback] wx[Weixin] bb[BlueBubbles] api["API Server
(OpenAI-compatible)"] @@ -75,6 +77,7 @@ flowchart TB dt --> store fs --> store wc --> store + wcb --> store wx --> store bb --> store api --> store @@ -180,6 +183,7 @@ MATRIX_ALLOWED_USERS=@alice:matrix.org DINGTALK_ALLOWED_USERS=user-id-1 FEISHU_ALLOWED_USERS=ou_xxxxxxxx,ou_yyyyyyyy WECOM_ALLOWED_USERS=user-id-1,user-id-2 +WECOM_CALLBACK_ALLOWED_USERS=user-id-1,user-id-2 # Or allow GATEWAY_ALLOWED_USERS=123456789,987654321 @@ -362,6 +366,7 @@ Each platform has its own toolset: | DingTalk | `hermes-dingtalk` | Full tools including terminal | | Feishu/Lark | `hermes-feishu` | 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 | | BlueBubbles | `hermes-bluebubbles` | 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) - [Feishu/Lark Setup](feishu.md) - [WeCom Setup](wecom.md) +- [WeCom Callback Setup](wecom-callback.md) - [Weixin Setup (WeChat)](weixin.md) - [BlueBubbles Setup (iMessage)](bluebubbles.md) - [Open WebUI + API Server](open-webui.md) diff --git a/website/docs/user-guide/messaging/wecom-callback.md b/website/docs/user-guide/messaging/wecom-callback.md new file mode 100644 index 000000000..466294276 --- /dev/null +++ b/website/docs/user-guide/messaging/wecom-callback.md @@ -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 diff --git a/website/sidebars.ts b/website/sidebars.ts index 52fd589c7..973cfe89c 100644 --- a/website/sidebars.ts +++ b/website/sidebars.ts @@ -108,6 +108,7 @@ const sidebars: SidebarsConfig = { 'user-guide/messaging/dingtalk', 'user-guide/messaging/feishu', 'user-guide/messaging/wecom', + 'user-guide/messaging/wecom-callback', 'user-guide/messaging/weixin', 'user-guide/messaging/bluebubbles', 'user-guide/messaging/open-webui', @@ -175,6 +176,7 @@ const sidebars: SidebarsConfig = { items: [ 'developer-guide/adding-tools', 'developer-guide/adding-providers', + 'developer-guide/adding-platform-adapters', 'developer-guide/memory-provider-plugin', 'developer-guide/context-engine-plugin', 'developer-guide/creating-skills',