feat(integration-ever-works): G-P4-6 part 2 — Ever Works agent mail as read-only Gauzy Inbox copies (optional, off by default)

Merged
EV
10d ago
+627-9

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 Inbox EMAIL item (source = EVER_WORKS, identity ever_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 and webUrl), linked to the agent employee and the synced task. Text body (≤ 100 000 chars); the Inbox UI shows EMAIL items through its sandboxed viewer. Linked AGENT_EMAIL_MIRROR with 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, default off) on the Entities and directions tab (UI domain list + i18n). Off, the events are skipped — before this PR they had no handler and sat deferred, retried hourly, forever.
  • Mail Gauzy handed to the agent itself (part 1's AGENT_EMAIL link) 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 from GET /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 replaceRecipients option (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.mailbox as a chip (mailboxLabelOf).
  • No migration; behind EVER_WORKS_ENABLED + GAUZY_INBOX_ENABLED and 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