feat: add stage2 customer gateway types#35
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for Telegram and WhatsApp customer gateways, updating the CLI, documentation, and internal gateway factory. New adapter classes were added to handle the specific payload normalization for these platforms. Review feedback identified potential runtime issues in the normalization logic, including a possible AttributeError when accessing nested Telegram chat data and an IndexError when processing WhatsApp message arrays.
| or "" | ||
| ) | ||
| sender_name = str(sender.get("first_name") or sender.get("username") or "") | ||
|
|
There was a problem hiding this comment.
Potential AttributeError if the chat key exists in the payload but its value is explicitly None. In such cases, payload.get("chat", {}) returns None, and calling .get("id") on it will fail. Using (payload.get("chat") or {}) is a safer pattern to handle both missing keys and explicit None values.
| external_conversation_id=str((payload.get("chat") or {}).get("id") or payload.get("conversation_id") or ""), |
| if isinstance(first_entry, dict): | ||
| changes = first_entry.get("changes") | ||
| if isinstance(changes, list) and changes: | ||
| raw_msg = changes[0] |
There was a problem hiding this comment.
Potential IndexError if value.get("messages") returns an empty list. The isinstance(..., list) check passes for empty lists, but accessing index [0] will crash. It is safer to check if the list is non-empty before accessing its elements.
messages = value.get("messages")
if isinstance(messages, list) and messages:
raw_msg = messages[0]There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3864e90a3b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return CustomerEvent( | ||
| gateway=payload.get("gateway", self.metadata.get("id", "telegram")), | ||
| gateway_role=payload.get("gateway_role", "customer"), | ||
| external_conversation_id=str(payload.get("chat", {}).get("id") or payload.get("conversation_id") or ""), |
There was a problem hiding this comment.
Preserve external_conversation_id in Telegram normalization
TelegramCustomerGateway.normalize does not read the canonical external_conversation_id field, so payloads that already follow the documented webhook event contract lose their conversation key here and often become "". In Workflow.run, an empty conversation id is replaced with external_customer_id, which collapses distinct threads for the same customer into one conversation and can corrupt ticket/message grouping for Telegram integrations that send pre-normalized webhook events.
Useful? React with 👍 / 👎.
| return CustomerEvent( | ||
| gateway=payload.get("gateway", self.metadata.get("id", "whatsapp")), | ||
| gateway_role=payload.get("gateway_role", "customer"), | ||
| external_conversation_id=str(payload.get("conversation_id") or payload.get("wa_id") or sender_id or ""), |
There was a problem hiding this comment.
Preserve external_conversation_id in WhatsApp normalization
WhatsAppCustomerGateway.normalize also ignores external_conversation_id and only derives the conversation from conversation_id/wa_id/sender id. For webhook clients that already send the standard external_conversation_id, this drops the provided thread identifier and causes Workflow.run to fall back to customer id, merging separate conversations for the same WhatsApp user.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2e20fb96f9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| sender = payload.get("from") | ||
| if not isinstance(sender, dict): | ||
| sender = payload.get("sender") |
There was a problem hiding this comment.
Parse Telegram sender from nested message payload
Telegram webhook updates carry sender info under message.from, but this normalization only looks for top-level from/sender, so sender_id stays empty for standard Telegram payloads unless callers prefill external_customer_id. In that case Workflow.run passes an empty external customer id into ensure_customer, which creates a new random customer record instead of reusing the prior one, breaking conversation continuity and customer history for repeated messages from the same Telegram user.
Useful? React with 👍 / 👎.
| event_id = payload.get("event_id") | ||
| if event_id is not None and not isinstance(event_id, str): | ||
| event_id = str(event_id) |
There was a problem hiding this comment.
Populate WhatsApp event_id from nested message data
After extracting entry[].changes[].value.messages[0], the code still sets event_id only from top-level payload["event_id"] and ignores the native WhatsApp message id in the nested message object. Because _build_dedup_event_id depends on event.id, repeated inbound texts from the same sender can collapse into the same dedup key and be treated as duplicates, dropping legitimate later messages when integrations forward raw WhatsApp webhook payloads.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a420c351c1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| payload.get("external_conversation_id") | ||
| or (payload.get("chat") or {}).get("id") | ||
| or payload.get("conversation_id") |
There was a problem hiding this comment.
Extract Telegram chat ID from nested message.chat
TelegramCustomerGateway.normalize derives external_conversation_id from top-level chat/conversation_id, but Telegram webhook updates place chat identity under message.chat.id. For standard Telegram payloads this leaves external_conversation_id empty, and Workflow.run then falls back to external_customer_id, which merges different chats (e.g., separate groups/threads for the same sender) into one conversation and corrupts message/ticket grouping.
Useful? React with 👍 / 👎.
| event_id = payload.get("event_id") | ||
| if event_id is not None and not isinstance(event_id, str): | ||
| event_id = str(event_id) |
There was a problem hiding this comment.
Use Telegram update_id when normalizing event IDs
This normalization only reads payload["event_id"], but native Telegram updates provide uniqueness via fields like update_id (and message date under message.date). As written, typical Telegram payloads produce event.id == None and empty timestamp, so repeated identical texts from the same sender collapse to the same _build_dedup_event_id and later messages can be dropped as duplicates by find_message_by_event.
Useful? React with 👍 / 👎.
Stage 2 foundation: add Telegram and WhatsApp as supported customer gateway types with runtime builder support, broaden gateway CLI validation, and add gateway type tests. Owner gateway types remain webhook/openclaw/hermes. This keeps behavior in webhook transport compatibility for bootstrap.