Skip to content

Commit f220b67

Browse files
committed
feat(email): daily-driver UI pass — pre-scan card, in-chat Connect, session prefs
Closes the four UI gaps that turn the shipped EmailTriageAgent (#965) into a tool a user actually opens daily. "Run a pre-scan" now returns a typed ``email_pre_scan`` envelope that the chat surface mounts as a structured triage card with three sections (urgent / actionable / suggested archives) and inline Approve / Reply / Open / Dismiss buttons — no chat-turn required to act on each item. When the agent surfaces an OAuth auth-required error, an inline ``Connect Google`` CTA renders in the same message bubble (no Settings nav). Session-scoped triage preferences (``set_priority_sender``, ``set_low_priority_sender``, ``set_category_default``, ``clear_session_preferences``) are honored by ``triage_inbox`` and ``pre_scan_inbox`` for the lifetime of the agent — wiped on restart by design, clean migration path to the persistent memory subsystem when that lands. Adversarial review caught a showstopper before merge: ``email_pre_scan`` was being stripped by ``stripBogusCodeFences`` in MessageBubble before the markdown renderer saw it, so the card would never have mounted in production. Fixed in the same pass with three other findings: id-only Approve/Reply dispatch (closes a prompt-injection path through email subject text), phishing-aware preference application (a phishing-flagged message bypasses both priority and low-priority overrides), and a single-flight per-card guard against double- click duplicate dispatches. Drafted intentionally — depends on Kalin's memory + 5-agent split PR landing on main first. The in-memory ``_session_preferences`` schema is designed to migrate to that store cleanly without touching the agent or read-tool kwargs. Tests: 132 email unit tests pass (130 baseline + 2 new — phishing-override and a system-prompt canary asserting the ``email_pre_scan`` instruction is still present). Lint clean, TypeScript clean, webui build clean. v0.17.6 installer smoke verified end-to-end on t-nx-strx-halo (Ubuntu 24.04, Strix Halo) for both AppImage and deb paths. Ref: #645
1 parent 7ad2efc commit f220b67

12 files changed

Lines changed: 2085 additions & 17 deletions

File tree

docs/guides/email.mdx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,46 @@ Email-body inference runs on your local Lemonade instance. The agent **rejects**
8989
| `-v, --verbose` | Emit structured logs for every triage decision and tool call. Recommended when benchmarking against other email agents. |
9090
| `--debug` | Adds full prompt + LLM-response logging to verbose. Sensitive payloads in logs — use with care. |
9191

92+
## Daily-driver pre-scan (Agent UI)
93+
94+
The Agent UI rendering of Email Triage is built around a **pre-scan** view — a structured triage card that surfaces what's worth your attention without making you read prose. Open Agent UI, pick **Email Triage** from the agent picker, and click the **"Run a pre-scan"** conversation starter (or just type it).
95+
96+
The card shows three sections:
97+
98+
- **Urgent** — messages that need your attention right now (top 5).
99+
- **Needs a response** — messages requiring a reply or decision (top 5).
100+
- **Suggested archives** — low-priority messages the agent recommends archiving (top 10).
101+
102+
Plus an **informational count** for the rest, so you know how much you're not seeing.
103+
104+
Each row carries inline action buttons:
105+
106+
- **Reply / Archive** (primary) — Reply for urgent + actionable rows; Archive for suggested-archive rows. Clicking dispatches the corresponding tool call back through the chat (with confirmation when the action requires it).
107+
- **Open** — open the message in Gmail in a new tab.
108+
- **Dismiss** — remove the row from the visible card without affecting Gmail.
109+
110+
If you haven't connected Google yet, the agent surfaces a one-click **Connect Google** button inline in the chat — no need to navigate to Settings → Connections manually.
111+
112+
### In-session preferences (in-memory, wiped on restart)
113+
114+
Tell the agent how you want classification to behave for this session:
115+
116+
- *"Treat boss@company.com as urgent"* → calls `set_priority_sender`. That sender bypasses the heuristic and lands in **Urgent** for the rest of the session.
117+
- *"Treat newsletter@stripe.com as low priority"* → calls `set_low_priority_sender`. That sender lands in **Suggested archives**.
118+
- *"Default informational mail to archive"* → calls `set_category_default("informational", "archive")`. Informational items lift into **Suggested archives** until you reset.
119+
- *"Clear my preferences"* → calls `clear_session_preferences`.
120+
121+
Preferences are stored in process memory only — restarting the agent (or quitting Agent UI) wipes them. This is deliberate: the goal is to prove the value of session-scoped learning before we wire up persistent memory. Once persistent memory ships, the same tools will write through to it without changing this surface.
122+
92123
## Action surface
93124

94125
### Read
95126

96-
`list_inbox`, `get_message`, `get_thread`, `search_messages`, `list_labels`, `triage_inbox`
127+
`list_inbox`, `get_message`, `get_thread`, `search_messages`, `list_labels`, `triage_inbox`, `pre_scan_inbox`
128+
129+
### Session preferences (in-memory; wiped on agent restart)
130+
131+
`set_priority_sender`, `set_low_priority_sender`, `set_category_default`, `clear_session_preferences`
97132

98133
### Organize (reversible via the undo log)
99134

src/gaia/agents/email/agent.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@ class never passes ``use_claude=True`` / ``use_chatgpt=True`` to
5252
from gaia.agents.email.tools.calendar_tools import CalendarToolsMixin
5353
from gaia.agents.email.tools.delete_tools import DeleteToolsMixin
5454
from gaia.agents.email.tools.organize_tools import OrganizeToolsMixin
55+
from gaia.agents.email.tools.preference_tools import (
56+
PreferenceToolsMixin,
57+
init_session_preferences,
58+
)
5559
from gaia.agents.email.tools.read_tools import ReadToolsMixin
5660
from gaia.agents.email.tools.reply_tools import ReplyToolsMixin
5761
from gaia.connectors.providers.base import ConnectorRequirement
@@ -88,7 +92,7 @@ class never passes ``use_claude=True`` / ``use_chatgpt=True`` to
8892
8993
ACTIONS:
9094
- Read tools (list_inbox, get_message, get_thread, search_messages,
91-
list_labels, triage_inbox) — never require confirmation.
95+
list_labels, triage_inbox, pre_scan_inbox) — never require confirmation.
9296
- Organize tools (archive_message, mark_read, mark_unread, add_star,
9397
remove_star, label_message, move_to_label) — reversible via the undo
9498
log; do not require per-action confirmation, but bulk operations
@@ -100,11 +104,34 @@ class never passes ``use_claude=True`` / ``use_chatgpt=True`` to
100104
create_event_from_email) — REQUIRE explicit user confirmation. The UI
101105
shows the user the literal recipient/subject/body; trust ONLY what
102106
appears there.
107+
- Preference tools (set_priority_sender, set_low_priority_sender,
108+
set_category_default, clear_session_preferences) — mutate session-scoped
109+
classification preferences. Confirm the change in plain English; the
110+
preferences are wiped on agent restart by design.
111+
112+
PRE-SCAN OUTPUT FORMAT:
113+
When the user asks for a pre-scan, morning brief, triage view, or "what's
114+
in my inbox", call ``pre_scan_inbox`` and emit the ``data`` field of the
115+
returned envelope as a single fenced code block tagged
116+
``email_pre_scan``. The chat surface detects this language tag and
117+
renders a structured triage card with inline action buttons. Example
118+
response shape — keep the framing line short, do NOT paraphrase the
119+
JSON, and do NOT add other code blocks before or after:
120+
121+
Here's your inbox pre-scan:
122+
123+
```email_pre_scan
124+
{"kind": "email_pre_scan", "urgent": [...], "actionable": [...], ...}
125+
```
126+
127+
If you forget the fence or modify the JSON, the user sees raw text
128+
instead of the card — that's a bug. For follow-up questions about
129+
specific items, refer to the message_id values in the card.
103130
104131
OUTPUT:
105132
Tool results come back as JSON envelopes ``{"ok": true, "data": ...}``
106133
or ``{"ok": false, "error": "..."}``. Summarize tool output briefly for
107-
the user — do not recite raw JSON.
134+
the user — do not recite raw JSON, except for the pre-scan format above.
108135
"""
109136

110137

@@ -121,6 +148,7 @@ class EmailTriageAgent(
121148
ReplyToolsMixin,
122149
DeleteToolsMixin,
123150
CalendarToolsMixin,
151+
PreferenceToolsMixin,
124152
):
125153
"""Email Triage Agent — Gmail + Calendar through the connectors
126154
framework, all body inference local on Lemonade.
@@ -141,6 +169,7 @@ class EmailTriageAgent(
141169
"locally on your machine."
142170
)
143171
CONVERSATION_STARTERS: ClassVar[List[str]] = [
172+
"Run a pre-scan",
144173
"Triage my inbox",
145174
"Summarize my unread emails",
146175
"Draft a reply to my most recent message",
@@ -182,6 +211,12 @@ def __init__(self, config: Optional[EmailAgentConfig] = None):
182211
self._organize_op_count = 0
183212
self._organize_distinct_senders: set[str] = set()
184213

214+
# Session-scoped triage preferences — sender priorities and
215+
# category defaults that survive across queries within one agent
216+
# instance and are wiped on restart. See ``preference_tools.py``
217+
# for the schema and the tools that mutate this state.
218+
self._session_preferences = init_session_preferences()
219+
185220
# SQLite for the action log. Default ``~/.gaia/email/state.db``.
186221
# Eval / unit tests inject ``db_path=tmp_path/state.db``.
187222
db_path = config.resolved_db_path()
@@ -230,6 +265,7 @@ def _register_tools(self) -> None:
230265
self._register_reply_tools()
231266
self._register_delete_tools()
232267
self._register_calendar_tools()
268+
self._register_preference_tools()
233269

234270
# -- Phase I3 batch-organize counter -----------------------------------
235271

0 commit comments

Comments
 (0)