From dd2e23c9af72bd3664db0ab127598087fc07fea4 Mon Sep 17 00:00:00 2001 From: holden093 Date: Tue, 16 Jun 2026 00:03:33 +0200 Subject: [PATCH] fix(agent): report phone numbers from resolve_contact when a matched contact has no email (#4327) When a CardDAV contact matched the search query but had no email address (only phone numbers), the tool silently dropped it and returned 'No contacts found'. Fall back to the contact's phone number(s) so the caller still receives usable information. Refs: #4178 (the contacts-domain classifier fix that made the model actually call resolve_contact for contacts queries, surfacing this pre-existing gap) --- src/tool_implementations.py | 17 ++++++++++++++--- src/tool_schemas.py | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/tool_implementations.py b/src/tool_implementations.py index 50a69d260..fac739e21 100644 --- a/src/tool_implementations.py +++ b/src/tool_implementations.py @@ -3797,7 +3797,7 @@ async def do_resolve_contact(content: str, owner: Optional[str] = None) -> Dict: if not name: return {"error": "name is required", "exit_code": 1} - contacts = {} # email -> {name, source} + contacts = {} # email_or_phone -> {name, source, phone?} # 1. CardDAV (Radicale) — structured contacts. Call in-process: a # server-side httpx GET to /api/contacts/search carries no session @@ -3812,10 +3812,18 @@ async def do_resolve_contact(content: str, owner: Optional[str] = None) -> Dict: match = q in hay_name or any(q in (e or "").lower() for e in c.get("emails", [])) if not match: continue + has_email = False for email in (c.get("emails") or []): email = (email or "").strip().lower() if email and "@" in email: contacts[email] = {"name": c.get("name") or email, "source": "contacts"} + has_email = True + # Fall back to phone numbers when the contact has no email address + if not has_email: + for phone in (c.get("phones") or []): + phone = (phone or "").strip() + if phone: + contacts[phone] = {"name": c.get("name") or phone, "source": "contacts", "phone": phone} except Exception: pass @@ -3835,8 +3843,11 @@ async def do_resolve_contact(content: str, owner: Optional[str] = None) -> Dict: return {"output": f"No contacts found matching '{name}'.", "exit_code": 0} lines = [f"Contacts matching '{name}':"] - for email, info in contacts.items(): - lines.append(f"- {info['name']} <{email}> ({info['source']})") + for key, info in contacts.items(): + if info.get("phone"): + lines.append(f"- {info['name']} — phone: {info['phone']} ({info['source']})") + else: + lines.append(f"- {info['name']} <{key}> ({info['source']})") return {"output": "\n".join(lines), "exit_code": 0} diff --git a/src/tool_schemas.py b/src/tool_schemas.py index b87ba7819..4393333c1 100644 --- a/src/tool_schemas.py +++ b/src/tool_schemas.py @@ -1009,7 +1009,7 @@ FUNCTION_TOOL_SCHEMAS = [ "type": "function", "function": { "name": "resolve_contact", - "description": "Look up a contact's email address by name. Searches CardDAV address book and sent email history. Use when the user says 'message [name]' or 'email [name]' without an email address.", + "description": "Look up a contact by name. Searches CardDAV address book and sent email history. Returns email addresses (when available) or phone numbers. Use when the user says 'message [name]', 'email [name]', or asks for someone's contact details.", "parameters": { "type": "object", "properties": {