fix(email): wire EmailSendRequest.provider fallback + exact pre-scan budget (#1603 review)#1616
Conversation
…ack (#1603) The send handler resolved the backend purely from the token's bound provider, so the declared request.provider field was dead in every path. It now acts as the fallback when the token carries no binding — the token's binding still wins, and an unbound token with no provider and two mailboxes connected is still rejected 400 ambiguous.
…#1603) _pre_scan_all_backends advanced its budget counter by the per-backend cap rather than the messages a backend actually returned, so the budget guard could trip a hair early when an inbox underfilled its cap. Count the real section totals plus informational instead; behavior is otherwise identical.
…crosoft (#1603) store.list_connections() hardcoded the provider set to ('google',), so every generic consumer was Microsoft-blind: api.list_connections() and get_connection('microsoft') returned nothing even with Microsoft connected, the /v1/connections endpoint and UI connectors list omitted it, and tripwire_check() never swept the Microsoft connection at startup. Enumerate the registered oauth_pkce providers instead (function-local registry import, same pattern as connected_mailbox_providers), so a newly added provider surfaces without editing this function.
Code Review — #1616
|
…1603) The budget regression test passed on both the buggy and fixed counters because with max_messages >= n_backends the per-backend over-count can never trip the early-exit guard. Rewrite it to the only scenario that diverges — max_messages < n_backends: an empty google inbox scanned first, ask for 1, microsoft must still be reached. RED on 'scanned += per_backend', GREEN on the actual-count fix. Also trims the 4-line WHY comment to one line per the comment guidance.
|
Addressed in You're right the prior test was green on both the old ( The behavior only diverges when
Renamed to |
Follow-up to #1614 (merged) — applies the claude.yml review suggestions plus the root-cause enumeration fix that #1614 only worked around.
Why this matters
#1614 shipped connector-derived mailbox selection, but two gaps remained:
store.list_connections()was hardcoded to("google",)— so every generic connection consumer stayed Microsoft-blind:api.list_connections(),api.get_connection("microsoft")(returnedNoneeven when Microsoft was connected), the/v1/connectionsREST endpoint, the UI connectors list, andtripwire_check()(never swept the Microsoft connection at startup). feat(email): connector-derived mailbox selection — multi-inbox triage + send (#1603, #1594) #1614'sconnected_mailbox_providers()patched only the email agent's path. This makes the primitive registry-driven (enumerate the registeredoauth_pkceproviders via a function-local import — no layering regression), so the whole system is consistent.EmailSendRequest.providerwas a public REST field that did nothing — with two mailboxes connected and an unbound confirmation token, a caller supplyingproviderto disambiguate still got a 400. Now wired as the unbound-token fallback (the token's own binding still wins when present).Plus a latent-correctness fix to the pre-scan budget guard (counts actual returned messages, not the per-backend cap).
Test plan
GAIA_MEMORY_DISABLED=1 python -m pytest tests/unit/connectors/ tests/unit/agents/email/ -x— green;util/lint.py --allcleanNew tests:
store.list_connections()finds microsoft-only / both;api.list_connections()+get_connection("microsoft")see Microsoft (no secret leak);TestSendRequestProviderFallback(unbound→routes there, bound-token wins, unbound+none+both→400); pre-scan budget counts actual returnedReal-world (Strix Halo, same persisted real Gmail + Outlook connections), before/after:
f768c2e, merged feat(email): connector-derived mailbox selection — multi-inbox triage + send (#1603, #1594) #1614)e694f00, this PR)store.list_connections()['google']['google','microsoft']api.list_connections()['google']['google','microsoft']api.get_connection('microsoft')NoneGET /v1/connections['google']['google','microsoft']Microsoft was connected the whole time — only the enumeration primitive changed.
Deferred from the review (own follow-up): surfacing the chosen
from:mailbox in thesend_nowconfirmation prompt — needs a base-agent confirmation hook touching all agents.