Преглед изворни кода

Carry inbound replies as MessageEvent.in_reply_to

The dispatch assembled its MessageEvent piecemeal without threading
info, so replies reached the agent's session unanchored. Build the full
event up front (text, source, id, timestamp, media) and let command
detection flow through message_event.is_command() instead of local
string special-casing.
Paul Klumpp пре 1 недеља
родитељ
комит
3f0f69fe03
2 измењених фајлова са 33 додато и 35 уклоњено
  1. 32 34
      adapter.py
  2. 1 1
      platform_config.py

+ 32 - 34
adapter.py

@@ -12,6 +12,7 @@ including both outbound messaging and realtime WebSocket connections.
 
 from __future__ import annotations
 
+from builtins import ImportError
 import random
 
 from gateway.platforms.helpers import MessageDeduplicator
@@ -673,13 +674,14 @@ class ChattoAdapter(BasePlatformAdapter):
         except RuntimeError:
             logger.warning("Chatto: chattolib event loop aborted - no client available")
             return
-    
-        logger.info("Chatto WS: 'message_posted' event_payload:%s", payload)
+        logger.info("Chatto WS: 'message_posted' payload:%s", payload)
 
         message = await payload.fetch_message(client=client)
         if message is None or message.deleted_at:
             return
         message_body = message.body or ""
+        logger.info("message: %s", message)
+
         attachments = list(message.attachments or [])
         # A message carrying only an image/PDF has an empty body — dropping it
         # here is what made attachments sent to Hermes disappear silently.
@@ -749,13 +751,13 @@ class ChattoAdapter(BasePlatformAdapter):
                 await self.add_reaction(message.room_id, message.id, "🫥")
             return
 
-        # Thread anchoring — if the incoming message is inside a thread, we
+        # Thread anchoring — if the incoming message is inside a Chatto thread, we
         # keep that thread by default; otherwise leave thread_id unset so
         # replies land at the root.
-        thread_id = payload.thread_root_event_id or None
-        if not thread_id and room_kind != RoomKind.DM and self.chatto_config.auto_thread.value:
+        thread_id = payload.thread_root_event_id or None # we could also take "payload.room_id" but then, we're in a thread already.
+        if not thread_id and room_kind != RoomKind.DM:
             thread_id = message.id
-            
+
         source = self.build_source(
             chat_id=payload.room_id,
             chat_name=self._room_names.get(message.room_id),
@@ -767,37 +769,33 @@ class ChattoAdapter(BasePlatformAdapter):
             role_authorized=True,
         )
 
-        msg_type = MessageType.COMMAND if (message_body.lstrip().startswith("/")) else MessageType.TEXT
-
-        my_body = message_body.lstrip() if msg_type == MessageType.COMMAND else message_body
+        # prepare a MessageEvent
+        message_event = MessageEvent(
+            text=message_body,
+            source=source,
+            message_id=message.id,
+            timestamp=message.created_at or datetime.now(timezone.utc),
+            raw_message=message,
+            reply_to_message_id=message.in_reply_to,
+        )
+        if message_event.is_command():
+            message_event.message_type = MessageType.COMMAND
 
         # Attachments — download and hand the local cache paths to the gateway,
         # which runs vision enrichment / document extraction off media_urls.
-        media_urls, media_types, media_kinds = await self._cache_attachments(
+        message_event.media_urls, message_event.media_types, media_kinds = await self._cache_attachments(
             payload.room_id, attachments,
         )
-        if media_kinds and msg_type != MessageType.COMMAND:
+        if media_kinds:
             # Same precedence as the Teams/Signal adapters: document-context
             # injection gates strictly on DOCUMENT, image handling keys off the
             # per-path image/* MIME regardless of message_type.
-            msg_type = self._message_type_for_media_kinds(media_kinds)
+            message_event.message_type = self._message_type_for_media_kinds(media_kinds)
+        else:
+            message_event.message_type = MessageType.TEXT
 
-        message_event = MessageEvent(
-            text=my_body,
-            source=source,
-            message_id=message.id,
-            message_type=msg_type,
-            media_urls=media_urls,
-            media_types=media_types,
-            timestamp=message.created_at or datetime.now(timezone.utc),
-            raw_message=message,
-            reply_to_message_id=message.id,
-            reply_to_text=message_body,
-            reply_to_author_id=user.id,
-            reply_to_author_name=user.login,
-        )
-        logger.info("Chatto: Dispatching MessageEvent to Hermes: %s", message_event)
 
+        logger.info("Chatto: Dispatching MessageEvent to Hermes: %s", message_event)
         await self.handle_message(message_event)
         return
 
@@ -1934,9 +1932,9 @@ class ChattoAdapter(BasePlatformAdapter):
 # ---------------------------------------------------------------------------
 
 async def hermes_standalone_sender_fn(
-    pconfig,
-    chat_id,
-    message,
+    pconfig: PlatformConfig,
+    chat_id: str,
+    message: str,
     *,
     thread_id=None,
     media_files=None,
@@ -1974,14 +1972,14 @@ async def hermes_standalone_sender_fn(
     try:
         kwargs: Dict[str, Any] = {}
         if chatto_config.auto_thread.value and thread_id:
-            kwargs["in_reply_to"] = thread_id
+            kwargs["thread_root_event_id"] = thread_id
         if media_files and media_files.get("attachment_asset_ids"):
             kwargs["attachment_asset_ids"] = list(media_files["attachment_asset_ids"])
         try:
             posted = await client.post_message(chat_id, message, **kwargs)
         except Exception as exc:
             return SendResult(success=False, error=str(exc))
-        return SendResult(success=True, message_id=getattr(posted, "id", "") or None)
+        return SendResult(success=True, message_id=posted.id, raw_response=posted)
     finally:
         try:
             await client.close()
@@ -2019,7 +2017,7 @@ def hermes_check_fn() -> bool:
     """Check if Chatto is configured and dependencies are available.
     Add real logic?! Or just .. there are no dependencies.. always return true. Really. Docs suck."""
     try:
-        import chattolib.client  # noqa: F401 — vendored dependency probe
+        from .vendor.common.chattolib import client  # noqa: F401 — vendored dependency probe
         return True
     except ImportError:
         return False
@@ -2100,7 +2098,7 @@ def hermes_env_enablement_fn() -> Optional[dict]:
     # Seed keys must be the config.yaml "extra" keys (config_key), NOT the env
     # var names — ChattoConfiguration reads extra[config_key].
     seed: Dict[str, Any] = {
-        ChattoConfiguration.base_url.config_key: (
+        ChattoConfiguration.base_url.field_name: (
             os.getenv(ChattoConfiguration.base_url.env_name) or ChattoClient.DEFAULT_BASE_URL
         ).strip(),
     }

+ 1 - 1
platform_config.py

@@ -27,7 +27,7 @@ import utils
 # own modules import each other absolutely ("from chattolib.x import y").
 # Importing it relatively as well would load a *second* copy of every module
 # under a different name, so isinstance() checks across the two would fail.
-from chattolib.client import ChattoClient
+from chattolib import ChattoClient
 
 logger = logging.getLogger(__name__)