feat(integration-ever-works): G-P4-6 part 2 — Ever Works agent mail as read-only Gauzy Inbox copies (optional, off by default)
Plan: Gauzy ↔ Ever Works integration, G-P4-6 EW agent bridge, part 2 (plan 04 §6 "EW agent mail → Gauzy (read-only mirror). Optional, per connection"); contract CR-11 (email.message.received|sent|status_changed, recipient-scoped, email:read; GET /email/messages/:id). Part 1 (#10846) is merged; this PR now targets develop. Owned by 7dae55c0/gw-gauzy-followups — other lanes please don't push to this branch.
What
- Inbound handler for
email_message(EverWorksAgentEmailMirror): an Ever Works agent email becomes a Gauzy InboxEMAILitem (source = EVER_WORKS, identityever_works_email/ the EW message id) of the Gauzy user paired with the agent's owner, listed under the virtual mailbox "Ever Works — " (metadata.mailbox,readOnly: true, from / to / cc / direction / the EW ids andwebUrl), linked to the agent employee and the synced task. Text body (≤ 100 000 chars); the Inbox UI shows EMAIL items through its sandboxed viewer. LinkedAGENT_EMAIL_MIRRORwith the EW version (an older or equal version is skipped; a newer one updates the copy). - Optional, off by default: a new direction domain
agentEmail(off|ew_to_gauzy, defaultoff) on the Entities and directions tab (UI domain list + i18n). Off, the events are skipped — before this PR they had no handler and satdeferred, retried hourly, forever. - Mail Gauzy handed to the agent itself (part 1's
AGENT_EMAILlink) is not copied back (it is in the Gauzy Inbox already). An owner not paired yet waits (deferred, like inbox items). A body left out of a large event (bodyOmitted, over 256 KiB) is read fromGET /email/messages/:id. Without the Gauzy Inbox: skipped when it is off, deferred when it is on but not loaded in that process. - Read-only: replying stays in Ever Works (the Inbox only answers decision kinds); the EW inbox source handler now ignores state changes of items that are not EW inbox items (a mirrored email has no inbox state in Ever Works).
- Follows the owner's pairing (review): the Inbox writer gains an additive
replaceRecipientsoption (IInboxItemUpsertInput) — on an update, a recipient the source no longer names loses the item (never all of them); the mirror sets it, every other source keeps add-only. - The virtual mailbox is shown (review): the Inbox detail pane shows
metadata.mailboxas a chip (mailboxLabelOf). - No migration; behind
EVER_WORKS_ENABLED+GAUZY_INBOX_ENABLEDand the new direction.
Tests
email/ever-works-agent-email.mirror.spec.ts: the copy (fields, recipient, virtual mailbox, links, version skip / update), off by default, our own hand-off skipped, unpaired owner deferred, undone agent pairing, omitted body read back, no Inbox (off vs not loaded); inbox.db.spec.ts replaceRecipients; inbox-ui.helpers.spec.ts mailboxLabelOf. Per the run rules not run locally (end-of-run full pass).
Checked locally: tsc --noEmit of the plugin (strict and the API build's loose settings), 0 errors in it; the UI plugin could not be type-checked here (the shared node_modules lost @angular/* in another lane's worktree removal, MAINTENANCE 01:2xZ) — its change is one entry of a constant map and one i18n key; prettier on the new files; cspell; egress static-hostnames; no Ever hostnames in the diff.
🤖 Generated with Claude Code