Skip to content

Add connected email accounts and draft-only agent tools - #897

Open
mpscholten wants to merge 6 commits into
masterfrom
codex/mail-accounts-runtime
Open

Add connected email accounts and draft-only agent tools#897
mpscholten wants to merge 6 commits into
masterfrom
codex/mail-accounts-runtime

Conversation

@mpscholten

@mpscholten mpscholten commented Sep 1, 2026

Copy link
Copy Markdown
Member

Summary

  • add secure Gmail and Microsoft OAuth/PKCE connections plus compatible custom IMAP accounts
  • expose provider-neutral tools for account/mailbox listing, search, bounded message reads, and private attachment downloads
  • add email_create_draft, email_update_draft, and email_reply_draft; all save provider-side drafts and return sent: false
  • keep this as a first-party runtime tool surface shared by every model backend rather than a user-configured MCP subprocess
  • expose a safe, normalized reply_to value so reply-draft approval shows the exact recipient

Safety and permissions

  • every draft write uses AlwaysConfirm: yolo, project/session grants, and remembered tool approvals cannot bypass a fresh user confirmation; plan mode and child agents cannot perform the write
  • no send tool, send endpoint, SMTP transport, or bare IMAP EXPUNGE exists
  • Gmail requests gmail.readonly + gmail.compose (Google defines compose broadly enough to permit send, but the runtime exposes no send path); Microsoft requests Mail.ReadWrite and never Mail.Send
  • custom IMAP drafts require a SPECIAL-USE \Drafts mailbox, synchronizing literals, and UIDPLUS; updates use exact UID/flag checks and UID EXPUNGE only
  • mailbox, message, attachment, and draft references are HMAC-authenticated with a per-runtime key and bound to their originating account and capability kind; attachment refs are also bound to the exact message
  • reply drafts re-fetch provider metadata and verify the approved recipient against the source message before writing
  • MIME headers/bodies, provider responses, attachment sizes, identifiers, and timeouts are bounded; mailbox content remains explicitly untrusted

Verification

  • focused mail suite: 44 examples, 0 failures
  • full agent-tui suite: 215 examples, 0 failures
  • full agent-cli run: 1,617 passed and 1 pending; the 23 tests initially blocked by macOS PostgreSQL socket-path length all passed in a 27/27 short-TMPDIR rerun
  • nix build .#agent-native-bridge
  • git diff --check and no-send/SMTP surface audit
  • independent final security review: no blockers or significant findings

The dependent macOS PR pins public commit 5ea817d5428f4c6d8a243ee34cd87c7b15f804c0.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-01T08:30:11.700456Z 45add7a PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 45add7afb8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

import Agent.CLI.Mail.OAuth
import Agent.CLI.Mail.Store (MailProvider(GmailProvider))
import Control.Concurrent (threadDelay)
import Control.Exception (bracket, finally)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Use the repository's safe exception API

This new test imports Control.Exception, although the repository requires Haskell code to prefer Control.Exception.Safe; import bracket and finally from the safe-exceptions module to keep exception handling consistent with the rest of the project.

AGENTS.md reference: AGENTS.md:L204-L205

Useful? React with 👍 / 👎.

Comment on lines +597 to +599
providerJson token (graphBase <> "/mailFolders")
[("$top", Just (BS8.pack (show maximum))),
("$select", Just "id,displayName,unreadItemCount")]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Traverse child folders when listing Outlook mailboxes

For Microsoft accounts with nested folders, Graph's /me/mailFolders endpoint returns only folders directly under the mailbox root. Because the tool requires mailbox IDs to come from this listing, child folders can never be selected or searched; recursively request each folder's childFolders collection, bounded by the configured maximum.

Useful? React with 👍 / 👎.

Comment on lines +835 to +839
let fetchLimit
| isJust request.mailSearchHasAttachments = 50
| otherwise = boundedCount 50 request.mailSearchLimit
uids = take fetchLimit
(reverse (concatMap searchUids searched))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Continue scanning IMAP results for attachment matches

When has_attachments is supplied, only the newest 50 UIDs are fetched before the attachment predicate is applied. If those 50 messages fail the predicate but older search hits satisfy it, email_search silently returns too few—or zero—results despite the requested limit; scan additional bounded batches until enough matching summaries are collected or the search results are exhausted.

Useful? React with 👍 / 👎.

Comment on lines +993 to +994
hasAttachment = "\"attachment\"" `Text.isInfixOf` joined
|| " attachment " `Text.isInfixOf` joined

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Detect filename-only IMAP attachments

For MIME parts whose BODYSTRUCTURE has a filename or name parameter but an INLINE or absent disposition, this substring check reports has_attachments = false, even though the MIME decoder later treats any filename-bearing part as an attachment. This makes summaries inconsistent with email_get and incorrectly removes such messages from has_attachments=true searches; parse the filename/name fields from BODYSTRUCTURE as attachment indicators too.

Useful? React with 👍 / 👎.

@mpscholten
mpscholten force-pushed the codex/mail-accounts-runtime branch from 45add7a to 7fc9ee3 Compare September 1, 2026 10:54
@mpscholten
mpscholten force-pushed the codex/mail-accounts-runtime branch from 7fc9ee3 to 981a1ae Compare September 1, 2026 14:15
@mpscholten mpscholten changed the title Add connected read-only email accounts Sep 1, 2026
@mpscholten mpscholten changed the title Add connected email accounts and draft-only tools Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant