Isolate WhatsApp group turns by speaker and clarify SQL scope denials.

Always tag group senders, inject group-focus system hints, label queued follow-ups by speaker, and mark insufficient_scope failures as non-retryable with a user-facing hint.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-10 23:11:41 +08:00
parent d2693d1c63
commit e91e4fe773
9 changed files with 167 additions and 31 deletions

View file

@ -48,9 +48,31 @@ def test_merge_channel_pending_jobs_zh_header() -> None:
{"user_text": "还有告警吗?", "lang": "zh"},
]
)
assert "一并回答" in str(merged.get("user_text") or "")
assert "1) 光功率?" in str(merged.get("user_text") or "")
assert "2) 还有告警吗?" in str(merged.get("user_text") or "")
text = str(merged.get("user_text") or "")
assert "分别回答" in text
assert "1) 光功率?" in text
assert "2) 还有告警吗?" in text
def test_merge_channel_pending_jobs_keeps_sender_labels() -> None:
merged = merge_channel_pending_jobs(
[
{
"user_text": "LOS count?",
"lang": "en",
"msg_metadata": {"group_sender_label": "Alice"},
},
{
"user_text": "offline NEs?",
"lang": "en",
"msg_metadata": {"group_sender_label": "Bob"},
},
]
)
text = str(merged.get("user_text") or "")
assert "[Alice]" in text
assert "[Bob]" in text
assert "own sender" in text.lower() or "Answer each" in text
def test_isolated_sessions_and_reset() -> None:

View file

@ -359,8 +359,14 @@ def test_build_group_sender_context() -> None:
metadata={"raw": {"pushName": "Alice"}},
external_user_id="111@s.whatsapp.net",
)
assert ctx == "[发言: Alice]"
assert ctx == "[Sender: Alice]"
assert "111@s.whatsapp.net" not in ctx
zh = build_group_sender_context(
metadata={"raw": {"pushName": "Alice"}},
external_user_id="111@s.whatsapp.net",
lang="zh",
)
assert zh == "[发言: Alice]"
def test_strip_bot_mentions_from_text() -> None:
@ -385,16 +391,17 @@ def test_normalize_mentioned_users_in_text_uses_nickname() -> None:
def test_prepare_group_user_text_for_model_user_in_chat() -> None:
out = prepare_group_user_text_for_model(
text="@162788605444170 每三分钟提醒@200846277140511 喝水",
metadata={"bot_lid": "162788605444170@lid", "bot_push_name": "oliver"},
metadata={"bot_lid": "162788605444170@lid", "bot_push_name": "oliver", "raw": {"pushName": "Bob"}},
mentions=["162788605444170@lid", "200846277140511@lid"],
bot_jid="8618142387786@s.whatsapp.net",
session_scope="user_in_chat",
external_user_id="8618142387786@s.whatsapp.net",
filtered_mention_jids=["200846277140511@lid"],
mention_names=["吴华"],
lang="zh",
)
assert out == "每三分钟提醒@吴华 喝水"
assert "[发言:" not in out
assert out.startswith("[发言: Bob]")
assert "每三分钟提醒@吴华 喝水" in out
def test_prepare_group_user_text_for_model_shared_chat_prefix() -> None:
@ -405,8 +412,9 @@ def test_prepare_group_user_text_for_model_shared_chat_prefix() -> None:
bot_jid="999@s.whatsapp.net",
session_scope="chat",
external_user_id="111@s.whatsapp.net",
lang="en",
)
assert out.startswith("[发言: Alice]")
assert out.startswith("[Sender: Alice]")
assert out.endswith("帮忙")
assert "@999" not in out
@ -415,6 +423,7 @@ def test_build_group_focus_instruction() -> None:
en = build_group_focus_instruction()
zh = build_group_focus_instruction(lang="zh")
assert "current sender" in en
assert "[Sender:" in en or "Sender" in en
assert "群聊规则" in zh
@ -742,8 +751,8 @@ def test_inbound_group_mention_uses_per_user_session_and_sender_prefix(
assert len(session_ids) == 2
assert session_ids[0] != session_ids[1]
assert "[群成员:" not in captured["text"]
assert "[发言:" not in captured["text"]
assert captured["text"] == "again"
assert captured["text"].startswith("[Sender: Bob]")
assert captured["text"].endswith("again")
assert "群聊规则" not in captured["text"]
sid = store.get_or_create_channel_session_v2(

View file

@ -954,3 +954,31 @@ def test_channel_file_delivery_hint_goes_to_system_not_user_message() -> None:
metadata={},
)
assert not OclawGateway._is_channel_delivery_channel(msg_admin)
def test_group_focus_system_hint_only_for_groups() -> None:
from runtime.types import StandardMessage
group_msg = StandardMessage(
session_id="s1",
tenant_id="t1",
user_id="u1",
role="member",
channel="whatsapp",
text="@bot alarms",
attachments=[],
metadata={"is_group": True},
)
dm = StandardMessage(
session_id="s1",
tenant_id="t1",
user_id="u1",
role="member",
channel="whatsapp",
text="alarms",
attachments=[],
metadata={"is_group": False},
)
hint = OclawGateway._group_focus_system_hint(group_msg, "en")
assert "current sender" in hint
assert OclawGateway._group_focus_system_hint(dm, "en") == ""

View file

@ -33,6 +33,10 @@ def test_enrich_mcp_scope_sql() -> None:
raw = {"ok": False, "error_code": "mcp_rpc_error_-32001", "error": "insufficient_scope:sql:query"}
out = enrich_mcp_scope_error(raw)
assert out["error_code"] == "insufficient_scope"
assert out.get("failure_class") == "auth"
assert out.get("retry_forbidden") is True
assert "aggregateUmeAlarms" in str(out.get("hint") or "")
assert "SQL query is not enabled" in str(out.get("user_facing_hint") or "")
assert out["required_scope"] == "sql:query"
assert "fallback_tools" in out
assert "ume_alarm_xlsx_report" in out["fallback_tools"]