diff --git a/gateway/platforms/feishu.py b/gateway/platforms/feishu.py index d9aaae9a74..24fd28c872 100644 --- a/gateway/platforms/feishu.py +++ b/gateway/platforms/feishu.py @@ -1060,6 +1060,10 @@ def _build_event_handler(self) -> Any: lambda data: self._on_reaction_event("im.message.reaction.deleted_v1", data) ) .register_p2_card_action_trigger(self._on_card_action_trigger) + # Suppress noisy SDK errors for unhandled events (#4789) + .register_p2_im_chat_access_event_bot_p2p_chat_entered_v1( + lambda data: None # Ignore bot p2p chat entered events + ) .build() ) @@ -1477,7 +1481,12 @@ def format_message(self, content: str) -> str: def _on_message_event(self, data: Any) -> None: """Normalize Feishu inbound events into MessageEvent.""" if self._loop is None: - logger.warning("[Feishu] Dropping inbound message before adapter loop is ready") + # WebSocket callback fires before the main loop is ready. + # Queue the event and retry after a short delay. + if not hasattr(self, "_pending_events"): + self._pending_events = [] + self._pending_events.append(data) + threading.Thread(target=self._drain_pending_events, daemon=True).start() return future = asyncio.run_coroutine_threadsafe( self._handle_message_event_data(data), @@ -1485,6 +1494,23 @@ def _on_message_event(self, data: Any) -> None: ) future.add_done_callback(self._log_background_failure) + def _drain_pending_events(self) -> None: + """Wait for the main loop, then replay queued events.""" + import time + deadline = time.time() + 5 + while time.time() < deadline: + loop = getattr(self, "_loop", None) + if loop: + for ev in getattr(self, "_pending_events", []): + asyncio.run_coroutine_threadsafe( + self._handle_message_event_data(ev), loop + ) + self._pending_events = [] + return + time.sleep(0.3) + logger.error("[Feishu] Loop never ready — dropped %d queued messages", len(getattr(self, "_pending_events", []))) + self._pending_events = [] + async def _handle_message_event_data(self, data: Any) -> None: """Shared inbound message handling for websocket and webhook transports.""" event = getattr(data, "event", None) diff --git a/gateway/run.py b/gateway/run.py index 225f82fa1f..f0a89f6993 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -2628,6 +2628,7 @@ async def _handle_message_with_agent(self, event, source, _quick_key: str): "platform": source.platform.value if source.platform else "", "user_id": source.user_id, "session_id": session_entry.session_id, + "chat_id": source.chat_id, "message": message_text[:500], } await self.hooks.emit("agent:start", hook_ctx) diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index 70b0b7b274..b5e6d7f05f 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -1107,6 +1107,15 @@ def run_gateway(verbose: int = 0, quiet: bool = False, replace: bool = False): """ sys.path.insert(0, str(PROJECT_ROOT)) + # Prevent multiple concurrent gateway instances (protects Feishu WebSocket) + # Multiple processes share lark_oapi global state → only last one receives events + if not replace and os.environ.get("HERMES_GATEWAY_REPLACING") != "1": + existing_pids = find_gateway_pids() + if existing_pids: + print(f"⚠️ Gateway already running (PIDs: {existing_pids}). Aborting.") + print(f" To replace it, run: hermes gateway run --replace") + sys.exit(1) + from gateway.run import start_gateway print("┌─────────────────────────────────────────────────────────┐")