c35e364562
Fixes two issues reported when creating an email campaign through the AI chat panel. ## 1. `save_campaign` failed with "Workspace auth context not set" The AI chat streams inside a queue worker job, where no HTTP middleware populates the async-local workspace auth context. The new `MessageCampaignDraftService.saveDraft()` relies on `executeInWorkspaceContext()`'s fallback to `getWorkspaceAuthContext()`, which throws outside HTTP requests. Database CRUD tools worked because `dispatchDatabaseCrud` builds an auth context explicitly; static tools had no equivalent. **Fix:** `ToolExecutorService.dispatch` (the single choke point for all tool executions: preloaded chat tools, `execute_tool`, MCP, workflow agents) now resolves the acting identity once, reusing a provided auth context or building a user context from `userId`/`userWorkspaceId`, and runs the dispatch inside `withWorkspaceAuthContext()`. This mirrors what `WorkspaceAuthContextMiddleware` does for HTTP requests, so tool code can rely on the async-local context on every transport. Side benefit: metadata tools executed from chat previously emitted metadata events with no user attribution (`MetadataEventEmitter` swallows the missing context); they are now attributed correctly. ## 2. AI changes to the open campaign required a page refresh The SSE pipeline delivers worker-originated record updates to the Apollo cache correctly. The campaign editor ignored them: `usePersistedCampaignDraft` seeds local draft state from the record once, and the subject/body/list inputs are uncontrolled (TipTap reads `defaultValue` on mount only). **Fix:** the draft hook now adopts upstream record values while the draft is pristine and exposes a `draftResyncKey` that remounts the `defaultValue`-seeded inputs. Unsaved local edits win over concurrent remote changes (last write wins on flush), and echoes of our own debounced persists never remount inputs mid-typing. ## Tests - `tool-executor.service.spec.ts`: auth context exposed to static tools, provided-context reuse, no-identity passthrough, no context leakage after dispatch, CRUD receives the resolved context, CRUD still rejects without identity. - `usePersistedCampaignDraft.test.tsx`: adopt-when-pristine, own-echo stability, dirty-draft-wins, adopt-after-persist. - `lint:diff-with-main` and `typecheck` clean on both packages. --- _Generated by [Claude Code](https://claude.ai/code/session_018nGvGhFahw1pcefb3P4iCk)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23811?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->