Productize scheduled success delivery, close WA pending, and clarify SQL scope grants.

Wrap successful scheduled jobs with a short WA header (+ attachment count), expire stale access pending and DM requesters on admin approve/deny, and add concrete next_steps for insufficient_scope:sql:query.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-10 23:52:41 +08:00
parent dff8901ae3
commit 4898adf7d0
10 changed files with 334 additions and 12 deletions

View file

@ -7,8 +7,12 @@ from pathlib import Path
from unittest.mock import patch
from runtime.extensions.whatsapp.access_control import denied_reply_text
from runtime.scheduler.turn_text import format_scheduled_failure_summary, format_scheduled_user_reminder
from runtime.scheduler.worker_turn import finalize_scheduled_turn_failure
from runtime.scheduler.turn_text import (
format_scheduled_failure_summary,
format_scheduled_success_summary,
format_scheduled_user_reminder,
)
from runtime.scheduler.worker_turn import finalize_scheduled_turn_failure, finalize_scheduled_turn_success
from svc.persistence.sqlite_store import SqliteStore
@ -24,6 +28,18 @@ class ScheduledFailureTextTests(unittest.TestCase):
self.assertIn("TimeoutError", text)
self.assertNotIn("定时", text)
def test_success_summary_wraps_body_and_attachments(self) -> None:
text = format_scheduled_success_summary(
job_name="Bandwidth watch",
reply_text="Critical: 3\nMajor: 1",
attachment_count=1,
lang="en",
)
self.assertIn("[Scheduled job done]", text)
self.assertIn("Bandwidth watch", text)
self.assertIn("Attachments: 1", text)
self.assertIn("Critical: 3", text)
def test_reminder_fallback_english(self) -> None:
self.assertIn("Reminder", format_scheduled_user_reminder("stretch", lang="en"))
self.assertIn("提醒", format_scheduled_user_reminder("活动一下", lang="zh"))
@ -101,6 +117,56 @@ class ScheduledFailureDeliveryTests(unittest.TestCase):
updated = self.store.scheduled_job_run_get(run_id=str(run.id), tenant_id=self.tenant_id)
self.assertEqual(str(getattr(updated, "status", "") or ""), "failed")
def test_finalize_success_enqueues_wrapped_summary(self) -> None:
job = self.store.scheduled_job_create(
tenant_id=self.tenant_id,
created_by_user_id=self.user_id,
name="License daily",
prompt_text="license report",
schedule_kind="cron",
schedule_expr="0 11 * * *",
lang="en",
delivery={
"whatsapp": {
"enabled": True,
"target_type": "group",
"chat_id": "120363011111111111@g.us",
"account_id": "wa-default",
}
},
timezone_name="UTC",
status="active",
)
run = self.store.scheduled_job_run_create(
job_id=str(job.id),
tenant_id=self.tenant_id,
scheduled_at="2026-08-10T00:00:00+00:00",
)
payload = {
"tenant_id": self.tenant_id,
"job_id": str(job.id),
"run_id_scheduled": str(run.id),
"lang": "en",
"resolved_channel": "whatsapp",
"resolved_chat_id": "120363011111111111@g.us",
"resolved_account_id": "wa-default",
"session_id": "",
}
finalize_scheduled_turn_success(
self.store,
task=None,
payload=payload,
base_result={"reply_text": "Found 2 license alarms.", "turn_uuid": "tu1"},
)
pending = self.store.list_pending_channel_outbound_messages(
channel="whatsapp", account_id="wa-default", limit=5
)
self.assertEqual(len(pending), 1)
body = str(pending[0].get("text") or "")
self.assertIn("[Scheduled job done]", body)
self.assertIn("License daily", body)
self.assertIn("Found 2 license alarms.", body)
if __name__ == "__main__":
unittest.main()

View file

@ -40,6 +40,9 @@ def test_enrich_mcp_scope_sql() -> None:
assert out["required_scope"] == "sql:query"
assert "fallback_tools" in out
assert "ume_alarm_xlsx_report" in out["fallback_tools"]
assert out.get("next_steps")
assert out.get("admin_action", {}).get("required_scope") == "sql:query"
assert "Admin" in str(out.get("user_facing_hint") or "")
def test_build_finalize_system_suffix() -> None:

View file

@ -20,6 +20,36 @@ def test_access_granted_guide_includes_ops_help() -> None:
assert "YES" in text or "continue" in text.lower()
def test_expire_stale_whatsapp_access_pending(tmp_path) -> None:
from datetime import datetime, timedelta, timezone
from svc.persistence.sqlite_store import SqliteStore
store = SqliteStore(str(tmp_path / "exp.sqlite"))
tenant = store.create_tenant("T")
tid = str(tenant["id"])
aid = "wa-default"
old_id = store.create_whatsapp_access_pending(
tenant_id=tid,
account_id=aid,
external_user_id="8611111111111@s.whatsapp.net",
phone="8611111111111",
request_text="hi",
)
assert old_id
# Backdate created_at beyond 7 days.
old_ts = (datetime.now(timezone.utc) - timedelta(days=10)).isoformat()
with store._connect() as conn: # noqa: SLF001
conn.execute(
"UPDATE whatsapp_access_pending SET created_at = ? WHERE id = ?",
(old_ts, old_id),
)
n = store.expire_stale_whatsapp_access_pending(tenant_id=tid, account_id=aid, older_than_hours=168)
assert n == 1
row = store.get_whatsapp_access_pending_by_id(pending_id=str(old_id))
assert row and str(row.get("status") or "") == "dismissed"
def test_menu_text_whatsapp_not_productivity_chinese() -> None:
text = _menu_text(channel="whatsapp")
assert "记待办" not in text