|
|
@@ -1686,6 +1686,30 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
# Sending (ConnectRPC — unchanged from polling version)
|
|
|
# ------------------------------------------------------------------ #
|
|
|
|
|
|
+ def _resolve_outbound_thread(
|
|
|
+ self,
|
|
|
+ chat_id: str,
|
|
|
+ reply_to: str | None,
|
|
|
+ metadata: dict[str, Any] | None,
|
|
|
+ ) -> str | None:
|
|
|
+ """The thread an outbound message belongs in, or ``None`` for the room.
|
|
|
+
|
|
|
+ One definition for every outbound path (text and attachments):
|
|
|
+ ``metadata["thread_id"]`` wins — it names the thread root to stay in;
|
|
|
+ otherwise ``reply_to`` anchors a thread under the incoming message,
|
|
|
+ but only with ``auto_thread`` enabled. DMs never thread.
|
|
|
+
|
|
|
+ Deliberately does NOT open an auto-thread of its own — only the
|
|
|
+ chunked text path in :meth:`send` does that, because only there do
|
|
|
+ further chunks follow into the freshly created thread.
|
|
|
+ """
|
|
|
+ thread_id = (metadata or {}).get("thread_id")
|
|
|
+ if reply_to and self.chatto_config.auto_thread.value and not thread_id:
|
|
|
+ thread_id = reply_to
|
|
|
+ if self._room_kinds.get(chat_id) == RoomKind.DM:
|
|
|
+ return None
|
|
|
+ return str(thread_id) if thread_id else None
|
|
|
+
|
|
|
async def send(
|
|
|
self,
|
|
|
chat_id: str,
|
|
|
@@ -1713,21 +1737,9 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
formatted = self.format_message(content)
|
|
|
chunks = self.truncate_message(formatted, ChattoConstants.MAX_MESSAGE_LENGTH)
|
|
|
|
|
|
- # Thread support — resolve thread_id once
|
|
|
- # DM rooms don't support threads, so skip threading for DMs
|
|
|
- thread_id = (metadata or {}).get("thread_id")
|
|
|
- # Only use reply_to as thread_id if auto_thread is enabled.
|
|
|
- # When auto_thread=false, responses go directly in the room
|
|
|
- # without threading under the incoming message.
|
|
|
- # If we already have thread_id from metadata, keep it (it's the
|
|
|
- # thread root); only use reply_to when we don't already have one.
|
|
|
- if reply_to and self.chatto_config.auto_thread.value and not thread_id:
|
|
|
- thread_id = reply_to
|
|
|
- # Check if this is a DM room — DMs don't support threads
|
|
|
+ thread_id = self._resolve_outbound_thread(chat_id, reply_to, metadata)
|
|
|
room_kind = self._room_kinds.get(chat_id)
|
|
|
is_dm = room_kind == RoomKind.DM
|
|
|
- if is_dm:
|
|
|
- thread_id = None
|
|
|
|
|
|
# Auto-thread: by default, Chatto creates a thread for replies to room
|
|
|
# messages (not DMs, not already in a thread). This keeps conversations
|
|
|
@@ -2332,10 +2344,14 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
reply_to: str | None,
|
|
|
metadata: dict[str, Any] | None,
|
|
|
) -> SendResult:
|
|
|
- """Post one message carrying already-uploaded assets."""
|
|
|
- thread_id = (metadata or {}).get("thread_id")
|
|
|
- if reply_to:
|
|
|
- thread_id = reply_to
|
|
|
+ """Post one message carrying already-uploaded assets.
|
|
|
+
|
|
|
+ Threading follows the same rules as a text send
|
|
|
+ (:meth:`_resolve_outbound_thread`), so with ``auto_thread`` disabled an
|
|
|
+ attachment reply lands in the room like its text counterpart instead of
|
|
|
+ quietly opening a thread.
|
|
|
+ """
|
|
|
+ thread_id = self._resolve_outbound_thread(chat_id, reply_to, metadata)
|
|
|
|
|
|
client = await self._get_chatto_client()
|
|
|
if client is None:
|
|
|
@@ -2347,7 +2363,7 @@ class ChattoAdapter(BasePlatformAdapter):
|
|
|
room_id=str(chat_id),
|
|
|
body=self.format_message(caption) if caption else "",
|
|
|
attachment_asset_ids=asset_ids,
|
|
|
- thread_root_event_id=str(thread_id) if thread_id else "",
|
|
|
+ thread_root_event_id=thread_id or "",
|
|
|
)
|
|
|
self._mark_seen(msg.id)
|
|
|
return SendResult(success=True, message_id=msg.id)
|