perf(mail): batch verify-on-read; test build_mail_content; addr-recall
Address PR #935 round-3 review: - search/verification.py: rewrite _verify_mail_messages to batch by mailbox. get_message triggers a server-side IMAP body fetch, so per-result verify issued one IMAP FETCH per hit; now it calls the DB-cached list_messages once per mailbox (mailbox_id comes from the Qdrant payload via result.metadata) and intersects — O(unique mailboxes) light calls instead of O(results) IMAP. - vector/mail_content.py: include Cc/Bcc in the indexed text so recipient queries match; move MAIL_SCAN_MAX_PER_MAILBOX here (shared by scanner index window + verifier presence window) with a note that it equals the Mail OCS per-request max (100), so it's a fixed constant not a config knob. - client/mail.py: clamp list_messages limit to 1..100 at the client layer. - tests: add test_mail_content.py (exact-layout contract for build_mail_content); rewrite the mail verifier tests for the batch-per-mailbox shape. Left as-is: ValidationError isn't caught in the list-endpoint tools — consistent with nc_notes_*/nc_deck_* and not a regression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c62ccf3d0d
commit
891d07db12
@@ -10,6 +10,13 @@ from typing import Any
|
||||
|
||||
from nextcloud_mcp_server.vector.html_processor import html_to_markdown
|
||||
|
||||
# Newest-N messages indexed (and verified) per mailbox. This equals the Mail
|
||||
# OCS API's per-request maximum (it clamps ``limit`` to 1..100), so it cannot be
|
||||
# raised without adding cursor pagination — hence a documented constant rather
|
||||
# than a config knob that would silently cap at 100. Shared by the scanner
|
||||
# (index window) and the verifier (presence window) so they stay consistent.
|
||||
MAIL_SCAN_MAX_PER_MAILBOX = 100
|
||||
|
||||
|
||||
def format_mail_addresses(addrs: list[dict[str, Any]] | None) -> str:
|
||||
"""Render a list of {label, email} address objects as a display string."""
|
||||
@@ -33,16 +40,21 @@ def build_mail_content(message: dict[str, Any]) -> str:
|
||||
<subject>
|
||||
From: <from>
|
||||
To: <to>
|
||||
Cc: <cc> # only when non-empty
|
||||
Bcc: <bcc> # only when non-empty
|
||||
<blank line>
|
||||
<body>
|
||||
|
||||
The body is the Mail OCS ``body`` field — sanitized HTML when
|
||||
``hasHtmlBody`` is set (converted to Markdown for embedding), otherwise
|
||||
Cc/Bcc are included so recipient-oriented queries ("emails where alice was
|
||||
cc'd") can match. The body is the Mail OCS ``body`` field — sanitized HTML
|
||||
when ``hasHtmlBody`` is set (converted to Markdown for embedding), otherwise
|
||||
plain text.
|
||||
"""
|
||||
subject = message.get("subject") or ""
|
||||
from_str = format_mail_addresses(message.get("from"))
|
||||
to_str = format_mail_addresses(message.get("to"))
|
||||
cc_str = format_mail_addresses(message.get("cc"))
|
||||
bcc_str = format_mail_addresses(message.get("bcc"))
|
||||
raw_body = message.get("body") or ""
|
||||
body_text = html_to_markdown(raw_body) if message.get("hasHtmlBody") else raw_body
|
||||
|
||||
@@ -51,6 +63,10 @@ def build_mail_content(message: dict[str, Any]) -> str:
|
||||
content_parts.append(f"From: {from_str}")
|
||||
if to_str:
|
||||
content_parts.append(f"To: {to_str}")
|
||||
if cc_str:
|
||||
content_parts.append(f"Cc: {cc_str}")
|
||||
if bcc_str:
|
||||
content_parts.append(f"Bcc: {bcc_str}")
|
||||
content_parts.append("") # Blank line
|
||||
content_parts.append(body_text)
|
||||
return "\n".join(content_parts)
|
||||
|
||||
Reference in New Issue
Block a user