Skip to content

refactor(agents): migrate email to hub (#1102)#1520

Merged
itomek merged 9 commits into
mainfrom
claudia/task-bb76ef4a
Jun 10, 2026
Merged

refactor(agents): migrate email to hub (#1102)#1520
itomek merged 9 commits into
mainfrom
claudia/task-bb76ef4a

Conversation

@kovtcharov-amd

Copy link
Copy Markdown
Collaborator

Why this matters

EmailTriageAgent was the last registry builtin still living in the core source tree. It now ships as the standalone gaia-agent-email wheel under hub/agents/python/email/, discovered via the gaia.agent entry point — so the core framework wheel no longer hardcodes it and it versions independently. This completes the chat-family/email wave of the #1102 framework-only-core restructure.

Unlike the analyst/browser migration (#1446), email owned a core API/MCP surface (email_routes.py, email_mcp.py). Per the restructure spec these move into the wheel (gaia_agent_email/api_routes.py, mcp_server.py); openai_server.py mounts the email router conditionally and logs an actionable pip install gaia-agent-email hint when the wheel is absent (no silent fallback). gaia email resolves through the registry and fails loudly with the same hint.

Test plan

  • python util/lint.py --black --isort — clean
  • email package parses; no residual gaia.agents.email / gaia.api.email_routes / email_mcp references in src/
  • CI: new Email Agent Unit Tests workflow installs the wheel and runs its suite
  • CI: core unit + API + CLI integration green without the wheel installed (guarded mount)
  • Reviewer: confirm Google-connector requirement, conversation starters, and local-inference (NPU/FLM) behavior preserved

Note: API/MCP surface relocation is the highest-risk area — review the guarded import in openai_server.py and the test importorskip guards.

Do not merge until CI is green and reviewed.

@github-actions github-actions Bot added dependencies Dependency updates devops DevOps/infrastructure changes cli CLI changes eval Evaluation framework changes tests Test changes performance Performance-critical changes agents labels Jun 4, 2026
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Clean, well-executed migration that moves EmailTriageAgent out of the core tree into the standalone gaia-agent-email wheel — mirroring the analyst/browser precedent (#1446) and finishing the #1102 framework-only-core wave. The registry/CLI/openai_server all degrade loudly with an actionable pip install hint when the wheel is absent, version pins match the sibling wheels exactly (0.1.0 / amd-gaia>=0.20.0), and the new smoke tests guard the literal build_registration() against drift from the agent's class attributes. src/ is free of residual gaia.agents.email / gaia.api.email_routes / email_mcp paths.

The one thing to fix before merge: six test files still import from gaia.api import email_routes, a module that this PR deleted. The PR's grep was scoped to src/, so it missed these — and because the affected tests importorskip the wheel (and the new email workflow doesn't run them), CI stays green while the never-auto-send security gate is no longer exercised anywhere.

Issues

🟡 Important — broken gaia.api.email_routes imports leave the never-auto-send gate untested

src/gaia/api/email_routes.py moved into the wheel (gaia_agent_email/api_routes.py), but six call sites still import the deleted module:

  • tests/integration/test_never_auto_send.py:205, 223, 253, 366
  • tests/test_api.py:1338, 1467

When the wheel is installed (local full-suite runs, or any future CI job that installs it), each from gaia.api import email_routes raises ModuleNotFoundError → test ERROR. When the wheel is absent, the module-level / setup() importorskip("gaia_agent_email") skips the whole file. The new test_email_agent.yml workflow runs only the unit suites — not tests/integration/ or tests/test_api.py — so the never-auto-send confirmation-gate tests now run in no CI job at all, even though the test plan lists that invariant as preserved.

All referenced symbols (resolve_send_backend, draft_reply, send_email) exist in the new module, so the fix is a path swap:

        from gaia_agent_email import api_routes as email_routes

Apply the same swap at all six locations. Then either add tests/integration/test_never_auto_send.py (and the REST e2e) to the email workflow, or confirm another wheel-installed job runs them — otherwise the gate's coverage is silently lost.

🟢 Minor — docs/spec/email-contract.mdx still points at the old module path

The contract spec references gaia.agents.email.contract and src/gaia/agents/email/contract.py, including a now-broken example from gaia.agents.email.contract import parse_request, parse_response (lines 6, 11, 33, 246). Update to gaia_agent_email.contract / hub/agents/python/email/... to keep the spec runnable. (The v0.17.6 release note and the planning docs reference the old path too, but those are historical — leave them.)

🟢 Minor — generate_mbox.py shows as a full rewrite for a one-line change

tests/fixtures/email/generate_mbox.py reports +1232/−1231 but the only substantive change is the single triage_heuristics import; the rest is line-ending normalization. Re-saving with the original line endings would shrink the diff to the one line a reviewer needs to see. Harmless either way — the generator's own line endings don't affect fixture output.

Strengths

  • Fail-loudly throughout. The guarded mount in openai_server.py, the gaia email / --spec handlers, and the benchmark lazy-import all raise/log an actionable pip install gaia-agent-email hint rather than degrading silently — exactly the project convention.
  • Drift-proof registration. build_registration() rebuilds connector requirements from the light scope constants (keeping discovery cheap), and test_required_connectors_match_agent / test_conversation_starters_match_agent assert the literals equal the agent's class attributes — verified to match.
  • PEP 562 lazy __init__ preserves the dependency-light contract import that the REST and MCP surfaces rely on, with test_contract_import_is_light locking it in.

Verdict

Request changes — the six stale gaia.api.email_routes imports must be repointed, and the never-auto-send tests need a CI home so the migration doesn't silently drop coverage of a security-critical invariant. Everything else is solid and consistent with the established hub-wheel pattern; the doc-sync and diff-noise items are minor.

@github-actions

Copy link
Copy Markdown
Contributor

🟡 tests/integration/test_never_auto_send.py:205,223,253,366 — stale import will break tests under CI.

src/gaia/api/email_routes.py was deleted and moved to the hub, but four monkeypatch-using tests still do:

from gaia.api import email_routes           # ← ImportError: module no longer exists
monkeypatch.setattr(email_routes, "resolve_send_backend", lambda: backend)

Because the CI job installs gaia-agent-email before running the email test suite, pytest.importorskip("gaia_agent_email") won't skip these — they'll collect successfully and then fail at the stale import.

Suggested fix for all four sites:

        from gaia_agent_email import api_routes as email_routes

The monkeypatch call (monkeypatch.setattr(email_routes, "resolve_send_backend", ...)) then patches the right module.

@itomek itomek force-pushed the claudia/task-bb76ef4a branch from 8520039 to b11299e Compare June 10, 2026 19:14
@github-actions

Copy link
Copy Markdown
Contributor

🟡 tests/integration/test_never_auto_send.py — four tests will crash with ModuleNotFoundError when gaia-agent-email is installed.

src/gaia/api/email_routes.py was moved to the hub wheel, but four test methods still do:

from gaia.api import email_routes
monkeypatch.setattr(email_routes, "resolve_send_backend", lambda: backend)

gaia.api.email_routes no longer exists, so any test that reaches these lines will raise ModuleNotFoundError.

The alias resolve_send_backend = get_send_backend is present in the new module, so the monkeypatch logic is otherwise correct once the import is fixed.

from gaia_agent_email import api_routes as email_routes

Apply that substitution to all four call sites (lines 205, 223, 253, 366).

itomek added 2 commits June 10, 2026 16:41
Brings the migration up to date with main (31 commits). Resolves the two
structural conflicts: agent.py keeps the gaia_agent_email tool-mixin imports
and picks up main's PhishingToolsMixin; main's new phishing_tools.py is
relocated into the hub package.
Make the migrated email agent pass CI against current main:
- Guard tests/unit/test_synthetic_mbox.py with importorskip(gaia_agent_email)
  so the generic unit-test job (no hub install) stops aborting collection.
- Rewrite main's phishing tests to the gaia_agent_email namespace + guard.
- Drop duplicate 'import pytest' (F811) in 10 migrated email tests.
- Registry creatability test uses reg.discover() — email is an installed
  wheel agent, registered via entry point, not a built-in.
- Align the email agent identity to installed:email everywhere (scopes.py,
  the AGENT_NOT_GRANTED message map, connectors docs, and the connectors/
  email tests). The registry enforces installed:<id> for wheel agents, so a
  stale builtin:email left the forwarded-grant scope-shortfall check unable
  to resolve the agent's required scopes (returned 201 instead of 403).
@github-actions github-actions Bot added the documentation Documentation changes label Jun 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔴 tests/test_api.py:1386 and tests/integration/test_never_auto_send.py:205,223,253,366from gaia.api import email_routes will raise ImportError at runtime because src/gaia/api/email_routes.py was moved to gaia_agent_email.api_routes and the gaia.api package has no re-export shim for it. Every test method that calls email_routes.resolve_send_backend, email_routes.send_email, or email_routes.draft_reply will crash.

Fix — swap the import in each affected location:

import gaia_agent_email.api_routes as email_routes

That one-liner replacement makes all downstream attribute accesses (email_routes.resolve_send_backend, email_routes.send_email, email_routes.draft_reply) and the monkeypatch.setattr(email_routes, …) calls target the correct module.

test_scope_shortfall_fails_loudly relied on the email agent being present in
the live registry via create_app()'s discover() — but that only registers the
agent when the gaia-agent-email wheel is pip-installed (entry-point metadata).
The connector test jobs install gaia core but not the wheel, so discover()
found no email agent, the router resolved no required scopes, and the forward
returned 201 instead of 403. Inject the email agent's registry entry directly
(the pattern TestRouterDrivenScopeResolution already uses) so the router's
scope-shortfall check is deterministic and independent of which agent wheels
are installed in the test env.
@github-actions

Copy link
Copy Markdown
Contributor

🔴 tests/integration/test_never_auto_send.py:205,223,253,366ImportError on every test in TestRestLayerGate and TestCrossSurfaceInvariant

src/gaia/api/email_routes.py was deleted in this PR (renamed to gaia_agent_email/api_routes.py), but three test methods still import it as a module reference for monkeypatching:

# line 205, 223, 253, 366 — all broken
from gaia.api import email_routes          # module no longer exists
monkeypatch.setattr(email_routes, "resolve_send_backend", ...)

The pytest.importorskip("gaia_agent_email") at the top of the file means these tests are intended to run when the wheel is installed — but gaia.api.email_routes won't be importable at all, so every test in those two classes will error rather than skip.

import gaia_agent_email.api_routes as email_routes

Apply that substitution at lines 205, 223, 253, and 366.

Address review feedback on the #1102 email→hub migration:
- Repoint 6 stale 'from gaia.api import email_routes' imports to
  'gaia_agent_email.api_routes' (the module moved into the wheel) in
  tests/integration/test_never_auto_send.py and tests/test_api.py. These
  would ModuleNotFoundError in any wheel-installed job.
- test_email_agent.yml: install .[dev,api] so fastapi is present for the REST
  spec tests (test_spec_html), and run tests/integration/test_never_auto_send.py
  so the never-auto-send confirmation gate has a wheel-installed CI home.
- Drop 'email' from test_reserved_id_rejected — email is no longer a reserved
  builtin id; it ships as the gaia-agent-email wheel and registers via the
  gaia.agent entry point (#1102).
- Update docs/spec/email-contract.mdx to the gaia_agent_email.contract path.
@itomek

itomek commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the thorough review — all addressed in 49b3cdc (pushed as a fast-forward).

🔴/🟡 Stale gaia.api.email_routes imports — repointed all 6 sites (tests/integration/test_never_auto_send.py ×4, tests/test_api.py ×2) to from gaia_agent_email import api_routes as email_routes. The monkeypatched symbols (resolve_send_backend, send_email, draft_reply) resolve against the new module.

🟡 Never-auto-send CI coveragetest_email_agent.yml now installs .[dev,api] (fastapi was missing, which was also failing the REST-spec tests) and runs tests/integration/test_never_auto_send.py, so the confirmation gate is exercised in a wheel-installed job. Verified locally with the wheel installed: full email-job scope 392 passed, and the never-auto-send + REST gate classes pass.

🟢 Doc spec — updated docs/spec/email-contract.mdx to gaia_agent_email.contract / the hub source path.

🟢 generate_mbox.py diff noise — left as-is per your note that it's harmless (the generator's line endings don't affect fixture output).

Also fixed two migration tails the review didn't cover but CI surfaced: test_reserved_id_rejected[email] (email is no longer a reserved builtin id — it ships as the wheel) and the connectors forwarded-grant scope test (the email agent's identity is installed:email, not builtin:email).

@github-actions

Copy link
Copy Markdown
Contributor

🟡 tests/fixtures/email/generate_mbox.py — the "runnable standalone" comment is now wrong and would mislead any developer who tries python generate_mbox.py without the hub wheel installed.

The old code imported from gaia.agents.email.tools.triage_heuristics, which lived inside the core package, so adding _REPO_ROOT to sys.path was sufficient. The new import is:

from gaia_agent_email.tools.triage_heuristics import ...

gaia_agent_email lives at hub/agents/python/email/ — it is not reachable via the repo root added to sys.path. Running the script standalone now fails with ModuleNotFoundError: No module named 'gaia_agent_email' unless the wheel is installed.

The comment still reads:

Keep the corpus runnable standalone (python generate_mbox.py) even when launched from this directory by putting the repo root on the path

That claim no longer holds. Either update the comment to say installation is now required, or add hub/agents/python/email to sys.path alongside the repo root so the standalone promise is kept:

_HUB_EMAIL = _REPO_ROOT / "hub" / "agents" / "python" / "email"
if str(_HUB_EMAIL) not in sys.path:
    sys.path.insert(0, str(_HUB_EMAIL))

CI isn't affected (the email workflow installs the wheel; test_synthetic_mbox.py guards with importorskip), but a developer running the fixture generator directly will get a confusing failure.

- isort the repointed email_routes imports in tests/test_api.py and
  tests/integration/test_never_auto_send.py — CI's isort groups the
  function-level gaia_agent_email imports (my pinned isort version didn't).
- generate_mbox.py: add the hub email package to sys.path so the fixture
  generator stays runnable standalone — the triage taxonomy moved into the
  gaia-agent-email wheel (#1102) and isn't reachable from the repo root alone.
  The importorskip guard in test_synthetic_mbox still skips in wheel-less jobs
  (it runs before generate_mbox is imported).
@itomek

itomek commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Both addressed:

🔴 Stale gaia.api.email_routes imports — already repointed in 49b3cdc (all four sites in test_never_auto_send.py are now from gaia_agent_email import api_routes as email_routes); this re-review predates that push. CI's isort then wanted those function-level imports grouped — fixed in 563561e (I'd linted with a pinned isort locally that didn't flag it; now matching CI's uvx isort).

🟡 generate_mbox.py standalone comment — fixed in 563561e: added the hub email package to sys.path alongside the repo root so python generate_mbox.py still runs standalone, and corrected the comment to note the taxonomy moved into the wheel. The importorskip guard in test_synthetic_mbox.py still skips wheel-less jobs — it runs before generate_mbox is imported, so the hub path is only added when the script actually executes.

Catch up with main (eval cost/TTFT harness #1546, OIDC publish-test fix #1576,
PyPI publish idempotency #1563). No conflicts.
@itomek itomek enabled auto-merge June 10, 2026 22:04
@kovtcharov-amd

Copy link
Copy Markdown
Collaborator Author

@claude review the latest and greatest!

@github-actions

Copy link
Copy Markdown
Contributor

All prior review items are resolved, and the migration is clean. One residual coverage gap remains around the relocated REST surface — details below.

Verified fixed ✅

  • Stale gaia.api.email_routes imports — all 6 sites now from gaia_agent_email import api_routes as email_routes (tests/integration/test_never_auto_send.py:204,220,249,360, tests/test_api.py:1386,1516). The monkeypatched symbols all exist in the new module (resolve_send_backend = get_send_backend alias at api_routes.py:435, plus send_email/draft_reply).
  • Never-auto-send CI hometest_email_agent.yml installs .[dev,api] + the wheel and runs test_never_auto_send.py, so the confirmation gate is exercised with the wheel present. Good catch on the missing fastapi.
  • generate_mbox.py standalone — the hub email dir is added to sys.path under an .is_dir() guard and the comment is corrected.
  • Doc spec, guarded mount, fail-loud CLIopenai_server.py:100-108 mounts conditionally with an actionable pip install hint; handle_email_command (cli.py:4584) fails loudly with the same hint. src/ is free of residual gaia.agents.email / email_routes / email_mcp paths.

🟡 Residual gap — the relocated REST surface still runs in no wheel-installed job

The never-auto-send gate is covered now, but the other half of the original suggestion ("…and the REST e2e") slipped through. The relocated /v1/email/* endpoints — which the PR's own test plan calls "the highest-risk area" — are exercised by tests/integration/test_email_rest_api_e2e.py and the email REST class in tests/test_api.py (both guard with importorskip("gaia_agent_email")), but:

  • test_api.yml runs tests/test_api.py without installing the wheel → those email tests skip.
  • test_email_agent.yml has the wheel but only runs test_never_auto_send.py — not tests/test_api.py nor test_email_rest_api_e2e.py.

Net: EmailTriageService and the REST endpoints relocate with zero e2e coverage in CI. Suggest adding tests/integration/test_email_rest_api_e2e.py (and the email portion of tests/test_api.py) to the test_email_agent.yml pytest list so the surface that moved is the surface that's tested.

🟢 Nits (non-blocking)

  • tests/test_api.py:1199 comment still reads "added by src/gaia/api/email_routes.py" — path moved.
  • openai_server.py catches ImportError around both the import and include_router. A genuine broken transitive import inside an installed gaia_agent_email.api_routes would be swallowed as "not installed." Standard optional-mount pattern, but narrowing the try to just the import would keep it honest with the no-silent-fallback rule.

Verdict: approve once the REST e2e tests have a wheel-installed CI home — everything else is solid and consistent with the analyst/browser hub-wheel precedent.

@itomek

itomek commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Reviewed the current state (8774dbe3) — fully green (65 checks), up to date with main, mergeable. Full transparency: I pushed the recent green-up commits to this branch, so rather than rubber-stamp my own work, here's an honest pass flagging what's worth your eyes.

Decisions worth your review

1. Email identity moved builtin:emailinstalled:email (~12 files). This is the one substantive change to your design. The registry hard-stamps installed:<id> on entry-point/wheel agents (registry.py:830, the anti-impersonation boundary), so the agent's runtime grant key (scopes.py), the AGENT_NOT_GRANTED message map (formatting.py), the connectors router resolution, and ~6 tests all had to follow — otherwise a UI-granted scope is stored under one key and checked under another. That mismatch was exactly why test_scope_shortfall_fails_loudly returned 201 instead of 403. Caveat: any pre-existing builtin:email grant would be orphaned — moot since email's unreleased, but flagging because it touches the grant ledger. If you'd rather email keep builtin:email for continuity, that's a registry-allowlist change instead and I can swap directions.

2. REST email-test coverage gap. I gave the never-auto-send confirmation gate a wheel-installed home (added test_never_auto_send.py to test_email_agent.yml). But the REST-layer tests in test_api.py (TestEmailTriageEndpoint / TestEmailSendConfirmationGate) importorskip the wheel and run in no wheel-installed job — so the /v1/email/triage + /v1/email/send endpoint tests currently skip everywhere. They're in-process TestClient tests and pass with the wheel; happy to wire them into test_email_agent.yml too — just didn't want to presume.

3. Standalone fixture generator. Added hub/agents/python/email to generate_mbox.py's sys.path so python generate_mbox.py still works (taxonomy moved into the wheel); the importorskip guard in test_synthetic_mbox is unaffected since it runs first.

Solid

Fail-loud preserved throughout, security send-gate covered, lint clean (matched to CI's uvx black/isort/flake8 after a pinned-version gotcha), and the migration itself is consistent with the analyst/browser hub-wheel precedent.

LGTM once you're comfortable with the identity call in (1).

Address the residual review gap on the #1102 email→hub migration:
- test_email_agent.yml now runs tests/integration/test_email_rest_api_e2e.py
  and the email REST classes from tests/test_api.py (TestEmailTriageEndpoint,
  TestEmailSendConfirmationGate). The /v1/email/* endpoints relocated into the
  wheel but were importorskip'd in every job (API Tests has no wheel; the email
  job didn't run them), so the moved surface had zero e2e coverage. Verified
  green with the wheel installed (33 passed).
- openai_server.py: gate the optional email-router mount on an
  'import gaia_agent_email' presence check, then import api_routes OUTSIDE the
  try/except — a genuine broken import inside an installed wheel now fails
  loudly instead of being swallowed as 'not installed' (no-silent-fallback).
- test_api.py: fix the stale 'added by src/gaia/api/email_routes.py' comment.
@itomek

itomek commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

All three addressed in b84ebd7:

🟡 REST surface had no wheel-installed CI hometest_email_agent.yml (which installs .[dev,api] + the wheel) now runs tests/integration/test_email_rest_api_e2e.py plus the email REST classes from tests/test_api.py (TestEmailTriageEndpoint, TestEmailSendConfirmationGate). The relocated /v1/email/* surface (EmailTriageService + endpoints) is now exercised with the wheel present. Verified locally with the wheel installed: 33 passed.

🟢 Stale comment (test_api.py:1199) — updated; it now points at gaia_agent_email.api_routes (mounted conditionally by openai_server) instead of the moved src/gaia/api/email_routes.py.

🟢 Silent-swallow in the optional mount (openai_server.py) — good catch, fixed to honor no-silent-fallback. The mount now gates on a lightweight import gaia_agent_email presence check in the try, and imports api_routes + include_router in the else — so a genuine broken import inside an installed wheel raises loudly instead of being misreported as 'not installed'. Verified both paths: wheel present → routes mount (tests pass); wheel absent → core server starts and logs the pip install hint.

The 'import gaia_agent_email' presence check tripped pylint W0611
(unused-import) — '# noqa: F401' only silences flake8, not pylint. Switch to
importlib.util.find_spec, which probes the wheel's presence without an import
side effect (no unused import), while keeping the fail-loud behavior: the real
api_routes import stays outside the gate, so a broken import in an installed
wheel still raises loudly.
@itomek itomek added this pull request to the merge queue Jun 10, 2026
Merged via the queue into main with commit d71565f Jun 10, 2026
72 of 73 checks passed
@itomek itomek deleted the claudia/task-bb76ef4a branch June 10, 2026 23:03
itomek added a commit to RheagalFire/gaia that referenced this pull request Jun 11, 2026
…md#1539) (amd#1590)

The `engine=llm` path added for amd#1452 was written against
`src/gaia/api/email_routes.py`, which the hub migration (amd#1520) deleted
— leaving the feature stranded on a conflicting branch. Body text was
silently truncated to 4 000 characters before reaching the local model,
cutting signal from long emails and threads (amd#1539 observation).

This PR ports both fixes to the hub's canonical location.

**After:** `POST /v1/email/triage?engine=llm` works end-to-end —
low-confidence results are escalated to the local Lemonade model
(Gemma-4-E4B) with the full email body and a 4 096-token output cap so
chain-of-thought reasoning fits. Thread bodies are now joined
newest-first. `EmailTriageResult.message_id` echoes the request ID back
to the caller.

## Test plan

- [x] `uv run python -m pytest hub/agents/python/email/tests/ -x` — 11
tests pass (including body-not-clipped and newest-first ordering
assertions)
- [x] Live smoke: `curl -s localhost:4200/v1/email/triage?engine=llm -d
'...'` returns 200 with `result.category` from LLM (not 502)
- [ ] `curl ... ?engine=openai` returns 422

Closes amd#1452
Closes amd#1539
pull Bot pushed a commit to bhardwajRahul/gaia that referenced this pull request Jun 12, 2026
…errors (amd#1592) (amd#1599)

Connecting a Google account in the Agent UI left the Email Triage agent
unusable: triage failed with a vague "no permissions" message that could
only be cleared from the CLI, and after the CLI grant, token refresh
failed with a generic "technical issue." Two root causes — the amd#1520 hub
migration renamed the per-agent grant key
`builtin:email`→`installed:email` (orphaning every existing grant), and
the in-chat "grant access" CTA never fired because the email tools
emitted `str(exc)` instead of the `AGENT_NOT_GRANTED:`-prefixed
`format_connector_error`. After this change: legacy grants migrate
automatically on startup; the in-chat CTA fires and points the user at
the existing in-UI grant control (no CLI step); token-refresh 401s
become actionable (names the account, distinguishes reconnect vs.
server-side client-secret); and the OAuth client secret is validated at
connect time so a "connected" account that can't refresh is caught
immediately.

Closes amd#1592

## Test plan
- [x] `pytest tests/unit/connectors/` — 534 passed, 5 skipped (incl. new
grant-migration, token-401, secret-validation, and email-tool-envelope
regression tests)
- [x] `python util/lint.py --all`
- [x] `cd src/gaia/apps/webui && npm run build` + vitest
(`EmailConnectCta` fires on the `AGENT_NOT_GRANTED:` prefix)
- [ ] Real-world (t-nx-strx-halo): connect Google → Email Triage →
"triage my last N emails" runs with **no** CLI grant; a stale
`builtin:email` grant migrates to `installed:email` on restart —
**pending live OAuth**

Note: auto-confer-on-connect was intentionally NOT added (consent scope)
— the in-UI one-click grant + migration satisfy the AC without
blanket-granting every agent.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents cli CLI changes dependencies Dependency updates devops DevOps/infrastructure changes documentation Documentation changes eval Evaluation framework changes performance Performance-critical changes tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants