mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 03:23:23 +08:00
fix(whatsapp): pass quoted UME alarm to agent when @ bot
Extract quotedText from reply context correctly and enrich inbound only after mention detection, keeping strict @-required group policy. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
538945fb66
commit
5336f8835f
4 changed files with 72 additions and 4 deletions
|
|
@ -687,10 +687,14 @@ def process_inbound_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|||
account = store.find_user_by_channel_account(channel=inbound.channel, account_id=account_id) or {}
|
||||
from runtime.orchestration.group_ingest import (
|
||||
build_group_sender_context,
|
||||
enrich_alert_group_question,
|
||||
extract_quoted_ume_alert_text,
|
||||
mentions_include_bot,
|
||||
metadata_mentions_bot,
|
||||
resolve_group_policy,
|
||||
session_user_key,
|
||||
should_process_group_inbound,
|
||||
text_mentions_bot,
|
||||
)
|
||||
|
||||
group_policy = resolve_group_policy(account=account)
|
||||
|
|
@ -844,8 +848,21 @@ def process_inbound_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
|||
elif not reply:
|
||||
user_text = (inbound.text or "").strip()
|
||||
if inbound.is_group:
|
||||
meta_for_group = inbound.metadata if isinstance(inbound.metadata, dict) else {}
|
||||
bot_reached = metadata_mentions_bot(meta_for_group) or mentions_include_bot(
|
||||
mentions=list(inbound.mentions or []),
|
||||
bot_jid=bot_jid,
|
||||
metadata=meta_for_group,
|
||||
) or text_mentions_bot(text=user_text, bot_jid=bot_jid)
|
||||
if bot_reached:
|
||||
quoted_alert = extract_quoted_ume_alert_text(metadata=meta_for_group)
|
||||
if quoted_alert:
|
||||
user_text = enrich_alert_group_question(
|
||||
user_text=user_text,
|
||||
quoted_alert=quoted_alert,
|
||||
)
|
||||
sender_ctx = build_group_sender_context(
|
||||
metadata=inbound.metadata if isinstance(inbound.metadata, dict) else {},
|
||||
metadata=meta_for_group,
|
||||
external_user_id=inbound.external_user_id,
|
||||
)
|
||||
user_text = f"{sender_ctx}\n{user_text}" if user_text else sender_ctx
|
||||
|
|
|
|||
|
|
@ -76,11 +76,14 @@ function unwrapMessage(m: proto.IMessage | null | undefined): proto.IMessage | n
|
|||
function messageContextInfo(m: proto.IMessage | null | undefined): proto.IContextInfo | null | undefined {
|
||||
const u = unwrapMessage(m);
|
||||
if (!u) return null;
|
||||
const extCtx = u.extendedTextMessage?.contextInfo;
|
||||
// Reply+@ messages usually carry quote + mentions on extendedTextMessage.contextInfo.
|
||||
if (extCtx && (extCtx.quotedMessage || extCtx.stanzaId)) return extCtx;
|
||||
const top = (u as proto.IMessage & { messageContextInfo?: proto.IMessageContextInfo }).messageContextInfo;
|
||||
const fromTop = top?.mentionedJid?.length ? top : null;
|
||||
return (
|
||||
(fromTop as unknown as proto.IContextInfo) ||
|
||||
u.extendedTextMessage?.contextInfo ||
|
||||
extCtx ||
|
||||
u.imageMessage?.contextInfo ||
|
||||
u.videoMessage?.contextInfo ||
|
||||
u.documentMessage?.contextInfo ||
|
||||
|
|
@ -129,11 +132,23 @@ function extractMentions(m: proto.IMessage | null | undefined): string[] {
|
|||
return raw.map((j) => String(j || "").trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
function extractQuoteContext(m: proto.IMessage | null | undefined): { participant: string; stanzaId: string } {
|
||||
function pickQuotedText(m: proto.IMessage | null | undefined): string {
|
||||
const ctx = messageContextInfo(m);
|
||||
const qm = ctx?.quotedMessage;
|
||||
if (!qm) return "";
|
||||
return pickText(qm);
|
||||
}
|
||||
|
||||
function extractQuoteContext(m: proto.IMessage | null | undefined): {
|
||||
participant: string;
|
||||
stanzaId: string;
|
||||
quotedText: string;
|
||||
} {
|
||||
const ctx = messageContextInfo(m);
|
||||
return {
|
||||
participant: String(ctx?.participant || "").trim(),
|
||||
stanzaId: String(ctx?.stanzaId || "").trim(),
|
||||
quotedText: pickQuotedText(m),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -937,7 +952,6 @@ async function main(): Promise<void> {
|
|||
const isReplyToBot = Boolean(
|
||||
botJidRaw &&
|
||||
quote.participant &&
|
||||
!mentions.length &&
|
||||
botIdentityJids.some((botId) => mentionMatchesBotIdentity(quote.participant, botId)),
|
||||
);
|
||||
|
||||
|
|
@ -952,6 +966,7 @@ async function main(): Promise<void> {
|
|||
messageTimestamp: (msg as any).messageTimestamp || null,
|
||||
quotedParticipant: quote.participant || null,
|
||||
quotedStanzaId: quote.stanzaId || null,
|
||||
quotedText: quote.quotedText || null,
|
||||
isReplyToBot,
|
||||
mentionsBot,
|
||||
mentionedJids: mentions,
|
||||
|
|
|
|||
|
|
@ -146,6 +146,24 @@ def is_reply_to_bot(*, metadata: dict[str, Any] | None, bot_jid: str | None) ->
|
|||
return False
|
||||
|
||||
|
||||
def extract_quoted_ume_alert_text(*, metadata: dict[str, Any] | None) -> str:
|
||||
raw = _metadata_raw(metadata)
|
||||
quoted_text = str(raw.get("quotedText") or raw.get("quoted_text") or "").strip()
|
||||
if quoted_text and (quoted_text.startswith("[UME") or "[UME Alarm" in quoted_text[:120]):
|
||||
return quoted_text
|
||||
return ""
|
||||
|
||||
|
||||
def enrich_alert_group_question(*, user_text: str, quoted_alert: str) -> str:
|
||||
body = str(user_text or "").strip()
|
||||
quote = str(quoted_alert or "").strip()
|
||||
if not quote:
|
||||
return body
|
||||
if not body:
|
||||
return f"[Quoted UME alarm]\n{quote}"
|
||||
return f"[Quoted UME alarm]\n{quote}\n\n[User question]\n{body}"
|
||||
|
||||
|
||||
def normalize_jids(jids: list[str]) -> set[str]:
|
||||
out: set[str] = set()
|
||||
for raw in jids or []:
|
||||
|
|
@ -318,6 +336,8 @@ __all__ = [
|
|||
"GroupPolicyConfig",
|
||||
"build_group_sender_context",
|
||||
"build_whatsapp_group_reply_metadata",
|
||||
"enrich_alert_group_question",
|
||||
"extract_quoted_ume_alert_text",
|
||||
"mentions_include_bot",
|
||||
"metadata_mentions_bot",
|
||||
"normalize_jid",
|
||||
|
|
|
|||
|
|
@ -221,6 +221,22 @@ def test_should_accept_group_trigger_without_mention() -> None:
|
|||
)
|
||||
|
||||
|
||||
def test_enrich_quoted_ume_alarm_when_mentioned() -> None:
|
||||
from runtime.orchestration.group_ingest import (
|
||||
enrich_alert_group_question,
|
||||
extract_quoted_ume_alert_text,
|
||||
)
|
||||
|
||||
meta = {"raw": {"quotedText": "[UME Alarm Raised]\nDevice: NE1", "mentionsBot": True}}
|
||||
assert extract_quoted_ume_alert_text(metadata=meta).startswith("[UME")
|
||||
enriched = enrich_alert_group_question(
|
||||
user_text="what happened?",
|
||||
quoted_alert="[UME Alarm Raised]\nDevice: NE1",
|
||||
)
|
||||
assert "[Quoted UME alarm]" in enriched
|
||||
assert "what happened?" in enriched
|
||||
|
||||
|
||||
def test_session_user_key_group_sentinel() -> None:
|
||||
assert session_user_key(is_group=True, external_user_id="111@s.whatsapp.net") == GROUP_SESSION_USER_SENTINEL
|
||||
assert session_user_key(is_group=False, external_user_id="111@s.whatsapp.net") == "111@s.whatsapp.net"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue