feat(gateway): add Feishu/Lark platform support (#3817)

Adds Feishu (ByteDance's enterprise messaging platform) as a gateway
platform adapter with full feature parity: WebSocket + webhook transports,
message batching, dedup, rate limiting, rich post/card content parsing,
media handling (images/audio/files/video), group @mention gating,
reaction routing, and interactive card button support.

Cherry-picked from PR #1793 by penwyp with:
- Moved to current main (PR was 458 commits behind)
- Fixed _send_with_retry shadowing BasePlatformAdapter method (renamed to
  _feishu_send_with_retry to avoid signature mismatch crash)
- Fixed import structure: aiohttp/websockets imported independently of
  lark_oapi so they remain available when SDK is missing
- Fixed get_hermes_home import (hermes_constants, not hermes_cli.config)
- Added skip decorators for tests requiring lark_oapi SDK
- All 16 integration points added surgically to current main

New dependency: lark-oapi>=1.5.3,<2 (optional, pip install hermes-agent[feishu])

Fixes #1788

Co-authored-by: penwyp <penwyp@users.noreply.github.com>
This commit is contained in:
Teknium
2026-03-29 18:17:42 -07:00
committed by GitHub
parent e314833c9d
commit ca4907dfbc
19 changed files with 6135 additions and 9 deletions

View File

@@ -146,6 +146,7 @@ def _deliver_result(job: dict, content: str) -> None:
"mattermost": Platform.MATTERMOST,
"homeassistant": Platform.HOMEASSISTANT,
"dingtalk": Platform.DINGTALK,
"feishu": Platform.FEISHU,
"email": Platform.EMAIL,
"sms": Platform.SMS,
}

View File

@@ -57,6 +57,7 @@ class Platform(Enum):
DINGTALK = "dingtalk"
API_SERVER = "api_server"
WEBHOOK = "webhook"
FEISHU = "feishu"
@dataclass
@@ -274,6 +275,9 @@ class GatewayConfig:
# Webhook uses enabled flag only (secrets are per-route)
elif platform == Platform.WEBHOOK:
connected.append(platform)
# Feishu uses extra dict for app credentials
elif platform == Platform.FEISHU and config.extra.get("app_id"):
connected.append(platform)
return connected
def get_home_channel(self, platform: Platform) -> Optional[HomeChannel]:
@@ -810,6 +814,33 @@ def _apply_env_overrides(config: GatewayConfig) -> None:
if webhook_secret:
config.platforms[Platform.WEBHOOK].extra["secret"] = webhook_secret
# Feishu / Lark
feishu_app_id = os.getenv("FEISHU_APP_ID")
feishu_app_secret = os.getenv("FEISHU_APP_SECRET")
if feishu_app_id and feishu_app_secret:
if Platform.FEISHU not in config.platforms:
config.platforms[Platform.FEISHU] = PlatformConfig()
config.platforms[Platform.FEISHU].enabled = True
config.platforms[Platform.FEISHU].extra.update({
"app_id": feishu_app_id,
"app_secret": feishu_app_secret,
"domain": os.getenv("FEISHU_DOMAIN", "feishu"),
"connection_mode": os.getenv("FEISHU_CONNECTION_MODE", "websocket"),
})
feishu_encrypt_key = os.getenv("FEISHU_ENCRYPT_KEY", "")
if feishu_encrypt_key:
config.platforms[Platform.FEISHU].extra["encrypt_key"] = feishu_encrypt_key
feishu_verification_token = os.getenv("FEISHU_VERIFICATION_TOKEN", "")
if feishu_verification_token:
config.platforms[Platform.FEISHU].extra["verification_token"] = feishu_verification_token
feishu_home = os.getenv("FEISHU_HOME_CHANNEL")
if feishu_home:
config.platforms[Platform.FEISHU].home_channel = HomeChannel(
platform=Platform.FEISHU,
chat_id=feishu_home,
name=os.getenv("FEISHU_HOME_CHANNEL_NAME", "Home"),
)
# Session settings
idle_minutes = os.getenv("SESSION_IDLE_MINUTES")
if idle_minutes:

3255
gateway/platforms/feishu.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -983,6 +983,7 @@ class GatewayRunner:
"EMAIL_ALLOWED_USERS",
"SMS_ALLOWED_USERS", "MATTERMOST_ALLOWED_USERS",
"MATRIX_ALLOWED_USERS", "DINGTALK_ALLOWED_USERS",
"FEISHU_ALLOWED_USERS",
"GATEWAY_ALLOWED_USERS")
)
_allow_all = os.getenv("GATEWAY_ALLOW_ALL_USERS", "").lower() in ("true", "1", "yes") or any(
@@ -991,7 +992,8 @@ class GatewayRunner:
"WHATSAPP_ALLOW_ALL_USERS", "SLACK_ALLOW_ALL_USERS",
"SIGNAL_ALLOW_ALL_USERS", "EMAIL_ALLOW_ALL_USERS",
"SMS_ALLOW_ALL_USERS", "MATTERMOST_ALLOW_ALL_USERS",
"MATRIX_ALLOW_ALL_USERS", "DINGTALK_ALLOW_ALL_USERS")
"MATRIX_ALLOW_ALL_USERS", "DINGTALK_ALLOW_ALL_USERS",
"FEISHU_ALLOW_ALL_USERS")
)
if not _any_allowlist and not _allow_all:
logger.warning(
@@ -1434,6 +1436,13 @@ class GatewayRunner:
return None
return DingTalkAdapter(config)
elif platform == Platform.FEISHU:
from gateway.platforms.feishu import FeishuAdapter, check_feishu_requirements
if not check_feishu_requirements():
logger.warning("Feishu: lark-oapi not installed or FEISHU_APP_ID/SECRET not set")
return None
return FeishuAdapter(config)
elif platform == Platform.MATTERMOST:
from gateway.platforms.mattermost import MattermostAdapter, check_mattermost_requirements
if not check_mattermost_requirements():
@@ -1500,6 +1509,7 @@ class GatewayRunner:
Platform.MATTERMOST: "MATTERMOST_ALLOWED_USERS",
Platform.MATRIX: "MATRIX_ALLOWED_USERS",
Platform.DINGTALK: "DINGTALK_ALLOWED_USERS",
Platform.FEISHU: "FEISHU_ALLOWED_USERS",
}
platform_allow_all_map = {
Platform.TELEGRAM: "TELEGRAM_ALLOW_ALL_USERS",
@@ -1512,6 +1522,7 @@ class GatewayRunner:
Platform.MATTERMOST: "MATTERMOST_ALLOW_ALL_USERS",
Platform.MATRIX: "MATRIX_ALLOW_ALL_USERS",
Platform.DINGTALK: "DINGTALK_ALLOW_ALL_USERS",
Platform.FEISHU: "FEISHU_ALLOW_ALL_USERS",
}
# Per-platform allow-all flag (e.g., DISCORD_ALLOW_ALL_USERS=true)

View File

@@ -34,6 +34,7 @@ _EXTRA_ENV_KEYS = frozenset({
"SIGNAL_ACCOUNT", "SIGNAL_HTTP_URL",
"SIGNAL_ALLOWED_USERS", "SIGNAL_GROUP_ALLOWED_USERS",
"DINGTALK_CLIENT_ID", "DINGTALK_CLIENT_SECRET",
"FEISHU_APP_ID", "FEISHU_APP_SECRET", "FEISHU_ENCRYPT_KEY", "FEISHU_VERIFICATION_TOKEN",
"TERMINAL_ENV", "TERMINAL_SSH_KEY", "TERMINAL_SSH_PORT",
"WHATSAPP_MODE", "WHATSAPP_ENABLED",
"MATTERMOST_HOME_CHANNEL", "MATTERMOST_REPLY_MODE",

View File

@@ -1322,6 +1322,35 @@ _PLATFORMS = [
"help": "The AppSecret from your DingTalk application credentials."},
],
},
{
"key": "feishu",
"label": "Feishu / Lark",
"emoji": "🪽",
"token_var": "FEISHU_APP_ID",
"setup_instructions": [
"1. Go to https://open.feishu.cn/ (or https://open.larksuite.com/ for Lark)",
"2. Create an app and copy the App ID and App Secret",
"3. Enable the Bot capability for the app",
"4. Choose WebSocket (recommended) or Webhook connection mode",
"5. Add the bot to a group chat or message it directly",
"6. Restrict access with FEISHU_ALLOWED_USERS for production use",
],
"vars": [
{"name": "FEISHU_APP_ID", "prompt": "App ID", "password": False,
"help": "The App ID from your Feishu/Lark application."},
{"name": "FEISHU_APP_SECRET", "prompt": "App Secret", "password": True,
"help": "The App Secret from your Feishu/Lark application."},
{"name": "FEISHU_DOMAIN", "prompt": "Domain — feishu or lark (default: feishu)", "password": False,
"help": "Use 'feishu' for Feishu China, or 'lark' for Lark international."},
{"name": "FEISHU_CONNECTION_MODE", "prompt": "Connection mode — websocket or webhook (default: websocket)", "password": False,
"help": "websocket is recommended unless you specifically need webhook mode."},
{"name": "FEISHU_ALLOWED_USERS", "prompt": "Allowed user IDs (comma-separated, or empty)", "password": False,
"is_allowlist": True,
"help": "Restrict which Feishu/Lark users can interact with the bot."},
{"name": "FEISHU_HOME_CHANNEL", "prompt": "Home chat ID (optional, for cron/notifications)", "password": False,
"help": "Chat ID for scheduled results and notifications."},
],
},
]

View File

@@ -28,6 +28,7 @@ PLATFORMS = {
"mattermost": "💬 Mattermost",
"matrix": "💬 Matrix",
"dingtalk": "💬 DingTalk",
"feishu": "🪽 Feishu",
}
# ─── Config Helpers ───────────────────────────────────────────────────────────

View File

@@ -140,7 +140,8 @@ PLATFORMS = {
"homeassistant": {"label": "🏠 Home Assistant", "default_toolset": "hermes-homeassistant"},
"email": {"label": "📧 Email", "default_toolset": "hermes-email"},
"matrix": {"label": "💬 Matrix", "default_toolset": "hermes-matrix"},
"dingtalk": {"label": "💬 DingTalk", "default_toolset": "hermes-dingtalk"},
"dingtalk": {"label": "💬 DingTalk", "default_toolset": "hermes-dingtalk"},
"feishu": {"label": "🪽 Feishu", "default_toolset": "hermes-feishu"},
"api_server": {"label": "🌐 API Server", "default_toolset": "hermes-api-server"},
"mattermost": {"label": "💬 Mattermost", "default_toolset": "hermes-mattermost"},
}

View File

@@ -58,6 +58,7 @@ homeassistant = ["aiohttp>=3.9.0,<4"]
sms = ["aiohttp>=3.9.0,<4"]
acp = ["agent-client-protocol>=0.8.1,<0.9"]
dingtalk = ["dingtalk-stream>=0.1.0,<1"]
feishu = ["lark-oapi>=1.5.3,<2"]
rl = [
"atroposlib @ git+https://github.com/NousResearch/atropos.git",
"tinker @ git+https://github.com/thinking-machines-lab/tinker.git",
@@ -83,6 +84,7 @@ all = [
"hermes-agent[acp]",
"hermes-agent[voice]",
"hermes-agent[dingtalk]",
"hermes-agent[feishu]",
]
[project.scripts]

View File

@@ -13,7 +13,7 @@ def _would_warn():
"SIGNAL_ALLOWED_USERS", "SIGNAL_GROUP_ALLOWED_USERS",
"EMAIL_ALLOWED_USERS",
"SMS_ALLOWED_USERS", "MATTERMOST_ALLOWED_USERS",
"MATRIX_ALLOWED_USERS", "DINGTALK_ALLOWED_USERS",
"MATRIX_ALLOWED_USERS", "DINGTALK_ALLOWED_USERS", "FEISHU_ALLOWED_USERS",
"GATEWAY_ALLOWED_USERS")
)
_allow_all = os.getenv("GATEWAY_ALLOW_ALL_USERS", "").lower() in ("true", "1", "yes") or any(
@@ -22,7 +22,7 @@ def _would_warn():
"WHATSAPP_ALLOW_ALL_USERS", "SLACK_ALLOW_ALL_USERS",
"SIGNAL_ALLOW_ALL_USERS", "EMAIL_ALLOW_ALL_USERS",
"SMS_ALLOW_ALL_USERS", "MATTERMOST_ALLOW_ALL_USERS",
"MATRIX_ALLOW_ALL_USERS", "DINGTALK_ALLOW_ALL_USERS")
"MATRIX_ALLOW_ALL_USERS", "DINGTALK_ALLOW_ALL_USERS", "FEISHU_ALLOW_ALL_USERS")
)
return not _any_allowlist and not _allow_all

2580
tests/gateway/test_feishu.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@ def _clear_auth_env(monkeypatch) -> None:
"SMS_ALLOWED_USERS",
"MATTERMOST_ALLOWED_USERS",
"MATRIX_ALLOWED_USERS",
"DINGTALK_ALLOWED_USERS",
"DINGTALK_ALLOWED_USERS", "FEISHU_ALLOWED_USERS",
"GATEWAY_ALLOWED_USERS",
"TELEGRAM_ALLOW_ALL_USERS",
"DISCORD_ALLOW_ALL_USERS",
@@ -30,7 +30,7 @@ def _clear_auth_env(monkeypatch) -> None:
"SMS_ALLOW_ALL_USERS",
"MATTERMOST_ALLOW_ALL_USERS",
"MATRIX_ALLOW_ALL_USERS",
"DINGTALK_ALLOW_ALL_USERS",
"DINGTALK_ALLOW_ALL_USERS", "FEISHU_ALLOW_ALL_USERS",
"GATEWAY_ALLOW_ALL_USERS",
):
monkeypatch.delenv(key, raising=False)

View File

@@ -372,7 +372,7 @@ Important safety rule: cron-run sessions should not recursively schedule more cr
},
"deliver": {
"type": "string",
"description": "Delivery target: origin, local, telegram, discord, slack, whatsapp, signal, matrix, mattermost, homeassistant, dingtalk, email, sms, or platform:chat_id or platform:chat_id:thread_id for Telegram topics. Examples: 'origin', 'local', 'telegram', 'telegram:-1001234567890:17585', 'discord:#engineering'"
"description": "Delivery target: origin, local, telegram, discord, slack, whatsapp, signal, matrix, mattermost, homeassistant, dingtalk, feishu, email, sms, or platform:chat_id or platform:chat_id:thread_id for Telegram topics. Examples: 'origin', 'local', 'telegram', 'telegram:-1001234567890:17585', 'discord:#engineering'"
},
"model": {
"type": "string",

View File

@@ -15,6 +15,7 @@ import time
logger = logging.getLogger(__name__)
_TELEGRAM_TOPIC_TARGET_RE = re.compile(r"^\s*(-?\d+)(?::(\d+))?\s*$")
_FEISHU_TARGET_RE = re.compile(r"^\s*((?:oc|ou|on|chat|open)_[-A-Za-z0-9]+)(?::([-A-Za-z0-9_]+))?\s*$")
_IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".webp", ".gif"}
_VIDEO_EXTS = {".mp4", ".mov", ".avi", ".mkv", ".3gp"}
_AUDIO_EXTS = {".ogg", ".opus", ".mp3", ".wav", ".m4a"}
@@ -128,6 +129,7 @@ def _handle_send(args):
"mattermost": Platform.MATTERMOST,
"homeassistant": Platform.HOMEASSISTANT,
"dingtalk": Platform.DINGTALK,
"feishu": Platform.FEISHU,
"email": Platform.EMAIL,
"sms": Platform.SMS,
}
@@ -198,6 +200,10 @@ def _parse_target_ref(platform_name: str, target_ref: str):
match = _TELEGRAM_TOPIC_TARGET_RE.fullmatch(target_ref)
if match:
return match.group(1), match.group(2), True
if platform_name == "feishu":
match = _FEISHU_TARGET_RE.fullmatch(target_ref)
if match:
return match.group(1), match.group(2), True
if target_ref.lstrip("-").isdigit():
return target_ref, None, True
return None, None, False
@@ -280,6 +286,13 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
from gateway.platforms.discord import DiscordAdapter
from gateway.platforms.slack import SlackAdapter
# Feishu adapter import is optional (requires lark-oapi)
try:
from gateway.platforms.feishu import FeishuAdapter
_feishu_available = True
except ImportError:
_feishu_available = False
media_files = media_files or []
# Platform message length limits (from adapter class attributes)
@@ -288,6 +301,8 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
Platform.DISCORD: DiscordAdapter.MAX_MESSAGE_LENGTH,
Platform.SLACK: SlackAdapter.MAX_MESSAGE_LENGTH,
}
if _feishu_available:
_MAX_LENGTHS[Platform.FEISHU] = FeishuAdapter.MAX_MESSAGE_LENGTH
# Smart-chunk the message to fit within platform limits.
# For short messages or platforms without a known limit this is a no-op.
@@ -351,6 +366,8 @@ async def _send_to_platform(platform, pconfig, chat_id, message, thread_id=None,
result = await _send_homeassistant(pconfig.token, pconfig.extra, chat_id, chunk)
elif platform == Platform.DINGTALK:
result = await _send_dingtalk(pconfig.extra, chat_id, chunk)
elif platform == Platform.FEISHU:
result = await _send_feishu(pconfig, chat_id, chunk, thread_id=thread_id)
else:
result = {"error": f"Direct sending not yet implemented for {platform.value}"}
@@ -777,6 +794,63 @@ async def _send_dingtalk(extra, chat_id, message):
return {"error": f"DingTalk send failed: {e}"}
async def _send_feishu(pconfig, chat_id, message, media_files=None, thread_id=None):
"""Send via Feishu/Lark using the adapter's send pipeline."""
try:
from gateway.platforms.feishu import FeishuAdapter, FEISHU_AVAILABLE
if not FEISHU_AVAILABLE:
return {"error": "Feishu dependencies not installed. Run: pip install 'hermes-agent[feishu]'"}
from gateway.platforms.feishu import FEISHU_DOMAIN, LARK_DOMAIN
except ImportError:
return {"error": "Feishu dependencies not installed. Run: pip install 'hermes-agent[feishu]'"}
media_files = media_files or []
try:
adapter = FeishuAdapter(pconfig)
domain_name = getattr(adapter, "_domain_name", "feishu")
domain = FEISHU_DOMAIN if domain_name != "lark" else LARK_DOMAIN
adapter._client = adapter._build_lark_client(domain)
metadata = {"thread_id": thread_id} if thread_id else None
last_result = None
if message.strip():
last_result = await adapter.send(chat_id, message, metadata=metadata)
if not last_result.success:
return {"error": f"Feishu send failed: {last_result.error}"}
for media_path, is_voice in media_files:
if not os.path.exists(media_path):
return {"error": f"Media file not found: {media_path}"}
ext = os.path.splitext(media_path)[1].lower()
if ext in _IMAGE_EXTS:
last_result = await adapter.send_image_file(chat_id, media_path, metadata=metadata)
elif ext in _VIDEO_EXTS:
last_result = await adapter.send_video(chat_id, media_path, metadata=metadata)
elif ext in _VOICE_EXTS and is_voice:
last_result = await adapter.send_voice(chat_id, media_path, metadata=metadata)
elif ext in _AUDIO_EXTS:
last_result = await adapter.send_voice(chat_id, media_path, metadata=metadata)
else:
last_result = await adapter.send_document(chat_id, media_path, metadata=metadata)
if not last_result.success:
return {"error": f"Feishu media send failed: {last_result.error}"}
if last_result is None:
return {"error": "No deliverable text or media remained after processing MEDIA tags"}
return {
"success": True,
"platform": "feishu",
"chat_id": chat_id,
"message_id": last_result.message_id,
}
except Exception as e:
return {"error": f"Feishu send failed: {e}"}
def _check_send_message():
"""Gate send_message on gateway running (always available on messaging platforms)."""
platform = os.getenv("HERMES_SESSION_PLATFORM", "")

View File

@@ -351,6 +351,12 @@ TOOLSETS = {
"includes": []
},
"hermes-feishu": {
"description": "Feishu/Lark bot toolset - enterprise messaging via Feishu/Lark (full access)",
"tools": _HERMES_CORE_TOOLS,
"includes": []
},
"hermes-sms": {
"description": "SMS bot toolset - interact with Hermes via SMS (Twilio)",
"tools": _HERMES_CORE_TOOLS,
@@ -360,7 +366,7 @@ TOOLSETS = {
"hermes-gateway": {
"description": "Gateway toolset - union of all messaging platform tools",
"tools": [],
"includes": ["hermes-telegram", "hermes-discord", "hermes-whatsapp", "hermes-slack", "hermes-signal", "hermes-homeassistant", "hermes-email", "hermes-sms", "hermes-mattermost", "hermes-matrix", "hermes-dingtalk"]
"includes": ["hermes-telegram", "hermes-discord", "hermes-whatsapp", "hermes-slack", "hermes-signal", "hermes-homeassistant", "hermes-email", "hermes-sms", "hermes-mattermost", "hermes-matrix", "hermes-dingtalk", "hermes-feishu"]
}
}

View File

@@ -21,6 +21,7 @@ Toolsets are named bundles of tools that you can enable with `hermes chat --tool
| `hermes-cli` | platform | `browser_back`, `browser_click`, `browser_close`, `browser_console`, `browser_get_images`, `browser_navigate`, `browser_press`, `browser_scroll`, `browser_snapshot`, `browser_type`, `browser_vision`, `clarify`, `cronjob`, `delegate_task`, `execute_code`, `ha_call_service`, `ha_get_state`, `ha_list_entities`, `ha_list_services`, `honcho_conclude`, `honcho_context`, `honcho_profile`, `honcho_search`, `image_generate`, `memory`, `mixture_of_agents`, `patch`, `process`, `read_file`, `search_files`, `send_message`, `session_search`, `skill_manage`, `skill_view`, `skills_list`, `terminal`, `text_to_speech`, `todo`, `vision_analyze`, `web_extract`, `web_search`, `write_file` |
| `hermes-api-server` | platform | _(same as hermes-cli)_ |
| `hermes-dingtalk` | platform | _(same as hermes-cli)_ |
| `hermes-feishu` | platform | _(same as hermes-cli)_ |
| `hermes-discord` | platform | _(same as hermes-cli)_ |
| `hermes-email` | platform | _(same as hermes-cli)_ |
| `hermes-gateway` | composite | Union of all messaging platform toolsets |

View File

@@ -0,0 +1,129 @@
---
sidebar_position: 11
title: "Feishu / Lark"
description: "Set up Hermes Agent as a Feishu or Lark bot"
---
# Feishu / Lark Setup
Hermes Agent integrates with Feishu and Lark as a full-featured bot. Once connected, you can chat with the agent in direct messages or group chats, receive cron job results in a home chat, and send text, images, audio, and file attachments through the normal gateway flow.
The integration supports both connection modes:
- `websocket` — recommended; Hermes opens the outbound connection and you do not need a public webhook endpoint
- `webhook` — useful when you want Feishu/Lark to push events into your gateway over HTTP
## How Hermes Behaves
| Context | Behavior |
|---------|----------|
| Direct messages | Hermes responds to every message. |
| Group chats | Hermes responds when the bot is addressed in the chat. |
| Shared group chats | By default, session history is isolated per user inside a shared chat. |
This shared-chat behavior is controlled by `config.yaml`:
```yaml
group_sessions_per_user: true
```
Set it to `false` only if you explicitly want one shared conversation per chat.
## Step 1: Create a Feishu / Lark App
1. Open the Feishu or Lark developer console:
- Feishu: <https://open.feishu.cn/>
- Lark: <https://open.larksuite.com/>
2. Create a new app.
3. In **Credentials & Basic Info**, copy the **App ID** and **App Secret**.
4. Enable the **Bot** capability for the app.
:::warning
Keep the App Secret private. Anyone with it can impersonate your app.
:::
## Step 2: Choose a Connection Mode
### Recommended: WebSocket mode
Use WebSocket mode when Hermes runs on your laptop, workstation, or a private server. No public URL is required.
```bash
FEISHU_CONNECTION_MODE=websocket
```
### Optional: Webhook mode
Use webhook mode only when you already run Hermes behind a reachable HTTP endpoint.
```bash
FEISHU_CONNECTION_MODE=webhook
```
In webhook mode, Hermes serves a Feishu endpoint at:
```text
/feishu/webhook
```
## Step 3: Configure Hermes
### Option A: Interactive Setup
```bash
hermes gateway setup
```
Select **Feishu / Lark** and fill in the prompts.
### Option B: Manual Configuration
Add the following to `~/.hermes/.env`:
```bash
FEISHU_APP_ID=cli_xxx
FEISHU_APP_SECRET=secret_xxx
FEISHU_DOMAIN=feishu
FEISHU_CONNECTION_MODE=websocket
# Optional but strongly recommended
FEISHU_ALLOWED_USERS=ou_xxx,ou_yyy
FEISHU_HOME_CHANNEL=oc_xxx
```
`FEISHU_DOMAIN` accepts:
- `feishu` for Feishu China
- `lark` for Lark international
## Step 4: Start the Gateway
```bash
hermes gateway
```
Then message the bot from Feishu/Lark to confirm that the connection is live.
## Home Chat
Use `/set-home` in a Feishu/Lark chat to mark it as the home channel for cron job results and cross-platform notifications.
You can also preconfigure it:
```bash
FEISHU_HOME_CHANNEL=oc_xxx
```
## Security
For production use, set an allowlist:
```bash
FEISHU_ALLOWED_USERS=ou_xxx,ou_yyy
```
If you leave the allowlist empty, anyone who can reach the bot may be able to use it.
## Toolset
Feishu / Lark uses the `hermes-feishu` platform preset, which includes the same core tools as Telegram and other gateway-based messaging platforms.

View File

@@ -6,7 +6,7 @@ description: "Chat with Hermes from Telegram, Discord, Slack, WhatsApp, Signal,
# Messaging Gateway
Chat with Hermes from Telegram, Discord, Slack, WhatsApp, Signal, SMS, Email, Home Assistant, Mattermost, Matrix, DingTalk, or your browser. The gateway is a single background process that connects to all your configured platforms, handles sessions, runs cron jobs, and delivers voice messages.
Chat with Hermes from Telegram, Discord, Slack, WhatsApp, Signal, SMS, Email, Home Assistant, Mattermost, Matrix, DingTalk, Feishu/Lark, or your browser. The gateway is a single background process that connects to all your configured platforms, handles sessions, runs cron jobs, and delivers voice messages.
For the full voice feature set — including CLI microphone mode, spoken replies in messaging, and Discord voice-channel conversations — see [Voice Mode](/docs/user-guide/features/voice-mode) and [Use Voice Mode with Hermes](/docs/guides/use-voice-mode-with-hermes).
@@ -27,6 +27,7 @@ flowchart TB
mm[Mattermost]
mx[Matrix]
dt[DingTalk]
fs[Feishu/Lark]
api["API Server<br/>(OpenAI-compatible)"]
wh[Webhooks]
end
@@ -328,6 +329,7 @@ Each platform has its own toolset:
| Mattermost | `hermes-mattermost` | Full tools including terminal |
| Matrix | `hermes-matrix` | Full tools including terminal |
| DingTalk | `hermes-dingtalk` | Full tools including terminal |
| Feishu/Lark | `hermes-feishu` | Full tools including terminal |
| API Server | `hermes` (default) | Full tools including terminal |
| Webhooks | `hermes-webhook` | Full tools including terminal |
@@ -344,5 +346,6 @@ Each platform has its own toolset:
- [Mattermost Setup](mattermost.md)
- [Matrix Setup](matrix.md)
- [DingTalk Setup](dingtalk.md)
- [Feishu/Lark Setup](feishu.md)
- [Open WebUI + API Server](open-webui.md)
- [Webhooks](webhooks.md)

View File

@@ -54,6 +54,7 @@ const sidebars: SidebarsConfig = {
'user-guide/messaging/mattermost',
'user-guide/messaging/matrix',
'user-guide/messaging/dingtalk',
'user-guide/messaging/feishu',
'user-guide/messaging/open-webui',
'user-guide/messaging/webhooks',
],