fix(platforms): carry the inbound message id into the session source everywhere

Same bug class as the Slack/Feishu picks: buzz, dingtalk, email, google_chat,
line, ntfy, photon, sms, teams, wecom and whatsapp already had the platform
message id on the MessageEvent but built the SessionSource without it, so
source.message_id consumers (reply anchor in run.py, /sethome synthetic-thread
check, relay _event_ids fallback, shutdown notice anchor) saw None. Only sites
where the id variable was already in scope are widened.
This commit is contained in:
teknium1
2026-09-12 07:07:44 -07:00
committed by Teknium
parent 156d3c454c
commit 719cb67bdb
11 changed files with 18 additions and 11 deletions
+1 -1
View File
@@ -1897,7 +1897,7 @@ class BuzzAdapter(BasePlatformAdapter):
message_type = MessageType.DOCUMENT
source = self.build_source(
chat_id=chat_id, chat_name=self._channel_names.get(chat_id, chat_id), chat_type=chat_type,
user_id=user_id, user_name=user_name, thread_id=thread_id,
user_id=user_id, user_name=user_name, thread_id=thread_id, message_id=message_id,
)
event = MessageEvent(
text=text, message_type=message_type, source=source, raw_message=raw_message, message_id=message_id,
+2 -1
View File
@@ -365,7 +365,8 @@ class DingTalkAdapter(BasePlatformAdapter):
if not text and not media_urls:
return logger.debug("[%s] Empty message, skipping", self.name)
source = self.build_source(chat_id=chat_id, chat_name=getattr(message, "conversation_title", None), chat_type="group" if is_group else "dm",
user_id=sender_id, user_name=sender_nick, user_id_alt=sender_staff_id if sender_staff_id else None)
user_id=sender_id, user_name=sender_nick, user_id_alt=sender_staff_id if sender_staff_id else None,
message_id=msg_id)
create_at = getattr(message, "create_at", None)
try:
timestamp = datetime.fromtimestamp(int(create_at) / 1000, tz=timezone.utc) if create_at else datetime.now(tz=timezone.utc)
+2 -1
View File
@@ -644,7 +644,8 @@ class EmailAdapter(BasePlatformAdapter):
event = MessageEvent(
text=text or "(empty email)", message_id=msg_data["message_id"],
message_type=MessageType.DOCUMENT if "document" in kinds else MessageType.PHOTO if "image" in kinds else MessageType.TEXT,
source=self.build_source(chat_id=sender_addr, chat_name=name, chat_type="dm", user_id=sender_addr, user_name=name),
source=self.build_source(chat_id=sender_addr, chat_name=name, chat_type="dm", user_id=sender_addr, user_name=name,
message_id=msg_data["message_id"]),
media_urls=[att["path"] for att in attachments], media_types=[att["media_type"] for att in attachments],
reply_to_message_id=msg_data["in_reply_to"] or None)
logger.info("[Email] New message from %s: %s", sender_addr, subject)
+1 -1
View File
@@ -920,7 +920,7 @@ class GoogleChatAdapter(BasePlatformAdapter):
# Email is the canonical id (allowlists use emails); the ``users/{id}``
# resource name moves to user_id_alt.
user_id=(sender_email or sender_name), user_name=sender.get("displayName") or sender_email or sender_name,
thread_id=session_thread_id, user_id_alt=(sender_name or None),
thread_id=session_thread_id, user_id_alt=(sender_name or None), message_id=msg.get("name") or None,
)
return MessageEvent(
text=text, message_type=message_type, source=source, raw_message=msg, message_id=msg.get("name") or None,
+2 -1
View File
@@ -582,7 +582,8 @@ class LineAdapter(BasePlatformAdapter):
if chat_type == "dm" and self._client: # best-effort typing indicator (DM only)
asyncio.create_task(self._client.loading(chat_id))
source_obj = self.build_source(
chat_id=chat_id, chat_type=chat_type, user_id=user_id, user_name=user_id, chat_name=chat_id)
chat_id=chat_id, chat_type=chat_type, user_id=user_id, user_name=user_id, chat_name=chat_id,
message_id=message_id)
await self.handle_message(MessageEvent(
text=text, message_type=_LINE_MESSAGE_TYPES.get(msg_type, MessageType.TEXT), source=source_obj,
raw_message=event, message_id=message_id, media_urls=media_urls, media_types=media_types))
+1 -1
View File
@@ -256,7 +256,7 @@ class NtfyAdapter(BasePlatformAdapter):
# NOT drive authorization, so user_id is fixed to the topic name.
topic = event.get("topic") or self._topic
source = self.build_source(
chat_id=topic, chat_name=topic, chat_type="dm", user_id=topic, user_name=topic)
chat_id=topic, chat_name=topic, chat_type="dm", user_id=topic, user_name=topic, message_id=msg_id)
unix_ts, timestamp = event.get("time"), datetime.now(tz=timezone.utc)
try:
timestamp = datetime.fromtimestamp(int(unix_ts), tz=timezone.utc) if unix_ts else timestamp
+1 -1
View File
@@ -759,7 +759,7 @@ class PhotonAdapter(BasePlatformAdapter):
def _event(text: str, mtype: MessageType = MessageType.TEXT, **kwargs: Any) -> MessageEvent:
source = self.build_source(chat_id=space_id, chat_name=space_id, chat_type=chat_type,
user_id=sender_id, user_name=sender_id or None)
user_id=sender_id, user_name=sender_id or None, message_id=message_id)
return MessageEvent(text=text, message_type=mtype, source=source, message_id=message_id,
raw_message=event, timestamp=timestamp, **kwargs)
if ctype in {"read", "read_receipt"}: # presence signal, not a user turn (receipts for our sends)
+2 -1
View File
@@ -259,7 +259,8 @@ class SmsAdapter(BasePlatformAdapter):
return _twiml_response()
logger.info("[sms] inbound from %s -> %s: %s", redact_phone(from_number), redact_phone(to_number), text[:80])
source = self.build_source(
chat_id=from_number, chat_name=from_number, chat_type="dm", user_id=from_number, user_name=from_number)
chat_id=from_number, chat_name=from_number, chat_type="dm", user_id=from_number, user_name=from_number,
message_id=message_sid)
event = MessageEvent(
text=text, message_type=MessageType.TEXT, source=source, raw_message=form, message_id=message_sid)
# Non-blocking: Twilio expects a fast response
+2 -1
View File
@@ -492,7 +492,8 @@ class TeamsAdapter(BasePlatformAdapter):
chat_type=_CHAT_TYPES.get(getattr(conv, "conversation_type", None) or "", "dm"),
user_id=str(user_id),
user_name=getattr(from_account, "name", None) or "",
guild_id=getattr(conv, "tenant_id", None) or self._tenant_id)
guild_id=getattr(conv, "tenant_id", None) or self._tenant_id,
message_id=msg_id)
media: list = [m for m in [await self._cache_attachment(a) for a in getattr(activity, "attachments", None) or []] if m]
media_kinds = [kind for _, _, kind in media] # media items are (path, media_type, kind)
msg_type = next((t for kind, t in _MEDIA_KIND_PRECEDENCE if kind in media_kinds), MessageType.TEXT)
+2 -1
View File
@@ -462,7 +462,8 @@ class WeComAdapter(WeComStreamMixin, WeComMediaMixin, ChatSendQueueMixin, BasePl
if not text and not media_urls:
logger.info("[%s] Empty WeCom message skipped: is_group=%s chat=%s msgtype=%r", self.name, is_group, chat_id, body.get("msgtype"))
return
source = self.build_source(chat_id=chat_id, chat_type="group" if is_group else "dm", user_id=sender_id or None, user_name=sender_id or None)
source = self.build_source(chat_id=chat_id, chat_type="group" if is_group else "dm", user_id=sender_id or None, user_name=sender_id or None,
message_id=msg_id)
event = MessageEvent(
text=text, message_type=message_type, source=source, raw_message=payload, message_id=msg_id, media_urls=media_urls, media_types=media_types,
reply_to_message_id=f"quote:{msg_id}" if has_reply_context else None, reply_to_text=reply_text if has_reply_context else None, timestamp=datetime.now(tz=timezone.utc),
+2 -1
View File
@@ -827,7 +827,8 @@ class WhatsAppAdapter(WhatsAppBehaviorMixin, BasePlatformAdapter):
return None
msg_type = self._classify_bridge_message(data)
source = self.build_source(chat_id=data.get("chatId", ""), chat_name=data.get("chatName"), chat_type="group" if data.get("isGroup", False) else "dm",
user_id=data.get("senderId"), user_name=data.get("senderName"))
user_id=data.get("senderId"), user_name=data.get("senderName"),
message_id=data.get("messageId"))
cached_urls, media_types = await self._collect_bridge_media(data, msg_type)
body = data.get("body", "")
if data.get("isGroup"):