mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-17 13:13:18 +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 {}
|
account = store.find_user_by_channel_account(channel=inbound.channel, account_id=account_id) or {}
|
||||||
from runtime.orchestration.group_ingest import (
|
from runtime.orchestration.group_ingest import (
|
||||||
build_group_sender_context,
|
build_group_sender_context,
|
||||||
|
enrich_alert_group_question,
|
||||||
|
extract_quoted_ume_alert_text,
|
||||||
|
mentions_include_bot,
|
||||||
metadata_mentions_bot,
|
metadata_mentions_bot,
|
||||||
resolve_group_policy,
|
resolve_group_policy,
|
||||||
session_user_key,
|
session_user_key,
|
||||||
should_process_group_inbound,
|
should_process_group_inbound,
|
||||||
|
text_mentions_bot,
|
||||||
)
|
)
|
||||||
|
|
||||||
group_policy = resolve_group_policy(account=account)
|
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:
|
elif not reply:
|
||||||
user_text = (inbound.text or "").strip()
|
user_text = (inbound.text or "").strip()
|
||||||
if inbound.is_group:
|
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(
|
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,
|
external_user_id=inbound.external_user_id,
|
||||||
)
|
)
|
||||||
user_text = f"{sender_ctx}\n{user_text}" if user_text else sender_ctx
|
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 {
|
function messageContextInfo(m: proto.IMessage | null | undefined): proto.IContextInfo | null | undefined {
|
||||||
const u = unwrapMessage(m);
|
const u = unwrapMessage(m);
|
||||||
if (!u) return null;
|
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 top = (u as proto.IMessage & { messageContextInfo?: proto.IMessageContextInfo }).messageContextInfo;
|
||||||
const fromTop = top?.mentionedJid?.length ? top : null;
|
const fromTop = top?.mentionedJid?.length ? top : null;
|
||||||
return (
|
return (
|
||||||
(fromTop as unknown as proto.IContextInfo) ||
|
(fromTop as unknown as proto.IContextInfo) ||
|
||||||
u.extendedTextMessage?.contextInfo ||
|
extCtx ||
|
||||||
u.imageMessage?.contextInfo ||
|
u.imageMessage?.contextInfo ||
|
||||||
u.videoMessage?.contextInfo ||
|
u.videoMessage?.contextInfo ||
|
||||||
u.documentMessage?.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);
|
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);
|
const ctx = messageContextInfo(m);
|
||||||
return {
|
return {
|
||||||
participant: String(ctx?.participant || "").trim(),
|
participant: String(ctx?.participant || "").trim(),
|
||||||
stanzaId: String(ctx?.stanzaId || "").trim(),
|
stanzaId: String(ctx?.stanzaId || "").trim(),
|
||||||
|
quotedText: pickQuotedText(m),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -937,7 +952,6 @@ async function main(): Promise<void> {
|
||||||
const isReplyToBot = Boolean(
|
const isReplyToBot = Boolean(
|
||||||
botJidRaw &&
|
botJidRaw &&
|
||||||
quote.participant &&
|
quote.participant &&
|
||||||
!mentions.length &&
|
|
||||||
botIdentityJids.some((botId) => mentionMatchesBotIdentity(quote.participant, botId)),
|
botIdentityJids.some((botId) => mentionMatchesBotIdentity(quote.participant, botId)),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -952,6 +966,7 @@ async function main(): Promise<void> {
|
||||||
messageTimestamp: (msg as any).messageTimestamp || null,
|
messageTimestamp: (msg as any).messageTimestamp || null,
|
||||||
quotedParticipant: quote.participant || null,
|
quotedParticipant: quote.participant || null,
|
||||||
quotedStanzaId: quote.stanzaId || null,
|
quotedStanzaId: quote.stanzaId || null,
|
||||||
|
quotedText: quote.quotedText || null,
|
||||||
isReplyToBot,
|
isReplyToBot,
|
||||||
mentionsBot,
|
mentionsBot,
|
||||||
mentionedJids: mentions,
|
mentionedJids: mentions,
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,24 @@ def is_reply_to_bot(*, metadata: dict[str, Any] | None, bot_jid: str | None) ->
|
||||||
return False
|
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]:
|
def normalize_jids(jids: list[str]) -> set[str]:
|
||||||
out: set[str] = set()
|
out: set[str] = set()
|
||||||
for raw in jids or []:
|
for raw in jids or []:
|
||||||
|
|
@ -318,6 +336,8 @@ __all__ = [
|
||||||
"GroupPolicyConfig",
|
"GroupPolicyConfig",
|
||||||
"build_group_sender_context",
|
"build_group_sender_context",
|
||||||
"build_whatsapp_group_reply_metadata",
|
"build_whatsapp_group_reply_metadata",
|
||||||
|
"enrich_alert_group_question",
|
||||||
|
"extract_quoted_ume_alert_text",
|
||||||
"mentions_include_bot",
|
"mentions_include_bot",
|
||||||
"metadata_mentions_bot",
|
"metadata_mentions_bot",
|
||||||
"normalize_jid",
|
"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:
|
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=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"
|
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