main
12518 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
10c0bed462 |
fix: harden email-group SES provisioning, cleanup, and inbound replay (#21046)
## Changes **Provisioning idempotent** (`aws-ses-register-domain.service.ts`) - Each SES create call (`CreateConfigurationSet`, event destination, contact list, tenant association) now swallow `AlreadyExistsException` via `.send().catch()`. - Retry after partial failure re-run every step, no blow up on "already exists". Before: one existing resource kill whole provision. **Workspace delete clean up cloud** (`workspace.service.ts`, `emailing-domain-workspace-cleanup.job.ts`, `emailing-domain.service.ts`) - On workspace delete, fetch domain list first, pass domains to cleanup job. - Cleanup now loop `driver.cleanupDomain(domain)` per domain + `deprovisionWorkspace`. Tear down SES identity/tenant/config-set, not just delete DB rows. - Before: DB rows gone, SES resources orphaned forever. Now: cloud match DB. **Inbound replay dedupe** (`ses-inbound-mail-handler.service.ts`) - Use `snsMessageId` as job id. SNS deliver same message twice → second is no-op. No duplicate inbound email import. |
||
|
|
08b1c5738d |
i18n - translations (#21050)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
c2df39405c |
Fix admin pannel server variable config tab (#21017)
## Before <img width="1046" height="490" alt="image" src="https://github.com/user-attachments/assets/450557de-fcf5-4b51-afdb-36c0c36e43d8" /> ## After <img width="1040" height="414" alt="image" src="https://github.com/user-attachments/assets/4a5fe2ab-85d6-4431-9397-6f81ae24055d" /> |
||
|
|
3e2c50c6cf |
i18n - translations (#21048)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
f67db8d8b2 |
i18n - translations (#21047)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
3cd2458fdf |
i18n - website translations (#21045)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
13f09d8946 |
[Dashboards] Remove gauge chart types and code (#20410)
Follow-up cleanup to #20172. |
||
|
|
961745e9ba |
Fix latency spike on application lookup (#21042)
Fixes https://discord.com/channels/1130383047699738754/1509645089062781058 Caching application entities to improve authentication latency |
||
|
|
41832c8d82 |
Fix workflow creation on view filtered by status (#21027)
Creating a workflow on a table with with a filter on status (eg: status is "active") failed because it added the status to createOneWorkflow (in order to have the record belonging to the view) - while createOneWorkflow throwed a 400 exception when attempting to create a workflow with a status (does not correpsond to a valid behaviour). Silently stripping status rom create workflow endpoints. |
||
|
|
3041ed3b6e |
chore: sync AI model catalog from models.dev (#21041)
Automated daily sync of `ai-providers.json` from [models.dev](https://models.dev). This PR updates pricing, context windows, and model availability based on the latest data. New models meeting inclusion criteria (tool calling, pricing data, context limits) are added automatically. Deprecated models are detected based on cost-efficiency within the same model family. **Please review before merging** — verify no critical models were incorrectly deprecated. Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com> |
||
|
|
6d550611d2 |
chore(deps): bump typescript from 5.9.2 to 5.9.3 (#20991)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.2 to 5.9.3. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/microsoft/TypeScript/releases">typescript's releases</a>.</em></p> <blockquote> <h2>TypeScript 5.9.3</h2> <p>Note: this tag was recreated to point at the correct commit. The npm package contained the correct content.</p> <p>For release notes, check out the <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-9/">release announcement</a></p> <ul> <li><a href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=milestone%3A%22TypeScript+5.9.0%22+is%3Aclosed+">fixed issues query for Typescript 5.9.0 (Beta)</a>.</li> <li><a href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=milestone%3A%22TypeScript+5.9.1%22+is%3Aclosed+">fixed issues query for Typescript 5.9.1 (RC)</a>.</li> <li><em>No specific changes for TypeScript 5.9.2 (Stable)</em></li> <li><a href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=milestone%3A%22TypeScript+5.9.3%22+is%3Aclosed+">fixed issues query for Typescript 5.9.3 (Stable)</a>.</li> </ul> <p>Downloads are available on:</p> <ul> <li><a href="https://www.npmjs.com/package/typescript">npm</a></li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/microsoft/TypeScript/commit/c63de15a992d37f0d6cec03ac7631872838602cb"><code>c63de15</code></a> Bump version to 5.9.3 and LKG</li> <li><a href="https://github.com/microsoft/TypeScript/commit/8428ca4cc8a7ecc9ac18dd0258016228814f5eaf"><code>8428ca4</code></a> 🤖 Pick PR <a href="https://redirect.github.com/microsoft/TypeScript/issues/62438">#62438</a> (Fix incorrectly ignored dts file fr...) into release-5.9 (#...</li> <li><a href="https://github.com/microsoft/TypeScript/commit/a131cac6831aa6532ea963d0cb3131b957cad980"><code>a131cac</code></a> 🤖 Pick PR <a href="https://redirect.github.com/microsoft/TypeScript/issues/62351">#62351</a> (Add missing Float16Array constructo...) into release-5.9 (#...</li> <li><a href="https://github.com/microsoft/TypeScript/commit/04243333584a5bfaeb3434c0982c6280fe87b8d5"><code>0424333</code></a> 🤖 Pick PR <a href="https://redirect.github.com/microsoft/TypeScript/issues/62423">#62423</a> (Revert PR 61928) into release-5.9 (<a href="https://redirect.github.com/microsoft/TypeScript/issues/62425">#62425</a>)</li> <li><a href="https://github.com/microsoft/TypeScript/commit/bdb641a4347af822916fb8cdb9894c9c2d2421dd"><code>bdb641a</code></a> 🤖 Pick PR <a href="https://redirect.github.com/microsoft/TypeScript/issues/62311">#62311</a> (Fix parenthesizer rules for manuall...) into release-5.9 (#...</li> <li><a href="https://github.com/microsoft/TypeScript/commit/0d9b9b92e2aca2f75c979a801abbc21bff473748"><code>0d9b9b9</code></a> 🤖 Pick PR <a href="https://redirect.github.com/microsoft/TypeScript/issues/61978">#61978</a> (Restructure CI to prepare for requi...) into release-5.9 (#...</li> <li><a href="https://github.com/microsoft/TypeScript/commit/2dce0c58af51cf9a9068365dc2f756c61b82b597"><code>2dce0c5</code></a> Intentionally regress one buggy declaration output to an older version (<a href="https://redirect.github.com/microsoft/TypeScript/issues/62163">#62163</a>)</li> <li>See full diff in <a href="https://github.com/microsoft/TypeScript/compare/v5.9.2...v5.9.3">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Weiko <corentin@twenty.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait <FelixMalfait@users.noreply.github.com> |
||
|
|
3c97d9648b |
fix(address): show saved address in record detail when street1 is null (#21033)
## Fixes #20084 ### Problem A saved address is visible in the **table view** but shows **"Empty"** in the **record detail page** when `addressStreet1` is `null`. This reproduces with the default seed data out of the box — e.g. **Google** (city "Mountain View", no street), **Microsoft** (Redmond), **Meta** (Menlo Park) — which is why several users reported hitting it immediately. ### Root cause The frontend zod schema required `addressStreet1` to be a **non-null** string: ```ts // isFieldAddressValue.ts export const addressSchema = z.object({ addressStreet1: z.string(), // ← required non-null addressStreet2: z.string().nullable(), ... }); ``` …but the backend composite type marks it `isRequired: false` (`address.composite-type.ts`), and the DB column is nullable. So the API legitimately returns `addressStreet1: null` when only other subfields are filled. The two views diverge on how they render: - **Record detail** gates the value behind `useIsFieldEmpty()` → `isFieldValueEmpty()`, which for addresses calls `isFieldAddressValue()`. With `addressStreet1: null` the `safeParse` **fails**, so `isFieldValueEmpty` returns `true` and the `"Empty"` placeholder is shown (`RecordInlineCellDisplayMode`). - **Table view** (`RecordTableCellDisplayMode`) renders `AddressFieldDisplay` directly with **no** empty check, so the address stays visible. This was a latent mismatch since the address guard was introduced. ### Fix Make `addressStreet1` nullable to match the backend and the other subfields: - `addressSchema` → `addressStreet1: z.string().nullable()` - `FieldAddressValue.addressStreet1` → `string | null` - `FieldAddressDraftValue.addressStreet1` → `string | null` (keeps the input/draft type consistent; the text input already renders `?? ''`) The change is strictly more permissive — persisting and the settings default-value form still accept string values; they now also accept `null`. ### Tests - `isFieldAddressValue.test.ts` — guard returns `true` for `addressStreet1: null` with other subfields filled. - `isFieldValueEmpty.test.ts` — new address coverage: empty address is empty; **`street1: null` + city filled is NOT empty**; normal address is not empty. (Added an `addressFieldDefinition` mock.) Both new assertions were confirmed to **fail before the fix** and pass after. ### Verification - `npx jest isFieldValueEmpty isFieldAddressValue normalize-address-field-value-for-persist` → 17 passed - `npx nx typecheck twenty-front` → pass - `npx nx lint:diff-with-main twenty-front` → 0 warnings, 0 errors |
||
|
|
3afdabb93e |
fix(dashboards): isolate pie chart slice labels per widget (#21034)
## Summary Fixes [#21014](https://github.com/twentyhq/twenty/issues/21014). When two pie chart widgets shared the same group-by field (and therefore the same slice ids) but used different aggregation operators (e.g. `count` vs `sum`), the arc-link labels would mirror between the two charts — both ending up showing either the count or the sum values, depending on render order. Center metrics stayed correct. **Root cause.** Nivo's `ArcLinkLabelsLayer` and `ArcsLayer` (from `@nivo/arcs`) wire `react-spring`'s `useTransition` with `keys: e => e.id`. When two `<ResponsivePie>` instances render with overlapping ids, the transitioned data bleeds across charts. The center metric is unaffected because it's computed by a separate hook (`usePieChartCenterMetricData`). **Fix.** Namespace the Nivo-computed slice id per widget by passing an `id` accessor to `<ResponsivePie>`: ```tsx id={(datum) => `${id}:${String(datum.id)}`} ``` Lookups inside the widget switch to `datum.data.id` (the original, un-namespaced id stored on the raw datum), so value/percentage formatting, the custom tooltip, and the legend hover-dim behavior all keep working. Touched files: - `GraphWidgetPieChart.tsx` — add `id` accessor - `CustomArcsLayer.tsx` — compare legend highlight against `datum.data.id` - `getPieChartFormattedValue.ts`, `getPieChartTooltipData.ts` — match on `datum.data.id` - Tests for both utils get a regression case covering the namespaced computed id ## Test plan - [ ] `npx jest getPieChartFormattedValue` ✅ - [ ] `npx jest getPieChartTooltipData` ✅ - [ ] `npx tsc --noEmit` ✅ - [ ] Manual: dashboard with two pies on the same group-by field, one `count` and one `sum`, "Display data label" on for both — confirm each chart shows its own metric on the slices, and the central total is unchanged. - [ ] Manual: hover a legend item — the matching slice in that chart stays solid while the others dim, and the sibling chart is not affected. - [ ] Manual: clicking a slice still drills into the correctly filtered view. |
||
|
|
25b0e0d091 |
fix: correct typo occurence -> occurrence in metadata-event-emitter.ts (#21036)
## Summary Fixes a spelling typo in `packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-emitter.ts`: - Variable name `occurence` → `occurrence` (4 references on lines 101, 103, 114, 115) ## Changes - `packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-emitter.ts` — rename misspelled variable Co-authored-by: james <li@jamesdeMacBook-Pro.local> |
||
|
|
a43e5c3fb3 |
i18n - translations (#21032)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
996cdaf3ff |
refactor(agents): split tool resolution into native and action rails (#20331)
## Summary
Splits AI agent tool resolution into two independent rails:
- **Native tools** — capabilities baked into the model SDK
(Anthropic/OpenAI `web_search`, xAI `web`/`x` provider options). Bound
by `NativeToolBinderService`, controlled by per-agent
`modelConfiguration` toggles. Opaque to Twenty — executed on the model
provider's servers.
- **Action tools** — registry-scoped tools from `ToolRegistryService`
(code interpreter, send email, record CRUD, etc.). Permission-gated via
the agent's role. Executed on Twenty's server.
Both rails merge into a single `ToolSet` at call time. When both
surfaces expose a search tool the model picks at runtime — coexistence
is intentional (relevant once Exa returns as an action, see below).
## Notable changes worth calling out
**Contract change: `AgentAsyncExecutorService.executeAgent` no longer
accepts `rolePermissionConfig`.** Workflow agents now scope exclusively
by the agent's own permission-tab role (`unionOf: [agentRoleId]`). The
previous role-merging path (caller role intersected with agent role) is
removed. No agent role → no registry tools (fail-closed by design).
**`NativeToolBinderService` relocated** from
`core-modules/tool-provider/native/` →
`metadata-modules/ai/ai-models/services/`. The binder needs SDK-package
knowledge, which lives in `ai-models`. Old location created a backwards
module dependency.
**`NATIVE_MODEL_TOOLS_BY_SDK_PACKAGE` is exhaustive over
`AiSdkPackage`** (`Record<>`, not `Partial<Record<>>`). Adding a new SDK
without thinking about native tools now fails the build. SDKs without
native tools (Bedrock, Google, Mistral, Azure, OpenAI-compatible) get
explicit `{}` entries.
**Discriminated union `kind: 'sdk-tool' | 'provider-option'`** lets one
registry describe both function tools (Anthropic/OpenAI) and runtime
sources (xAI). Follows the local `tool-provider` convention from #19321.
## Deferred to follow-ups
- **Exa web search is dropped from this PR** (along with its
`WEB_SEARCH_TOOL` permission flag and the Exa-specific gating). Exa
comes back as an **action/app tool** once apps can define permission
flags through the SDK — ongoing work in #20481.
- **xAI native search currently errors.** xAI deprecated its Live Search
API (the `web`/`x` provider-option sources this rail maps to), so xAI
returns `410` when native search is actually exercised. The code path
itself is clear — it's only hit if you test xAI native tools. Fixed
separately alongside the broader xAI model fixes.
## Conscious non-decisions
- **No "twenty-native" category.** `native` is reserved for
model/provider SDK features; everything Twenty-owned is just a
tool/action.
- **Coexistence over precedence.** No rule forcing an action search tool
to override native search (or vice-versa) — when both exist, it's the
user's choice in workflow agents and the model's choice in chat.
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
|
||
|
|
1d84695fb0 |
Fix: focus stack overwritten when auto-opening title cell on new record (#21029)
Fixes https://github.com/twentyhq/twenty/issues/20894 In `PageChangeEffect` `resetFocusStackToFocusItem` ran right after `openNewRecordTitleCell`, wiping the title cell entry. Typing in the auto-opened breadcrumb input (e.g. new workflow) triggered global shortcuts and ignored Enter / Escape / Tab. Reordered so the page reset runs first, then the title cell push lands on top. ## Before https://github.com/user-attachments/assets/d3c0c266-a493-46b8-b99b-32f4381b8664 ## After https://github.com/user-attachments/assets/3a886386-f438-46d2-a6ea-9ee6908d6df2 |
||
|
|
64e0b76d00 |
chore(deps): bump js-cookie from 3.0.5 to 3.0.7 (#20992)
Bumps [js-cookie](https://github.com/js-cookie/js-cookie) from 3.0.5 to 3.0.7. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/js-cookie/js-cookie/releases">js-cookie's releases</a>.</em></p> <blockquote> <h2>v3.0.7</h2> <ul> <li>Prevent cookie attribute injection: CVE-2026-46625 (eb3c40e)</li> <li>Add <code>Partitioned</code> attribute to readme (b994768)</li> <li>Publish to npm registry via trusted publisher exclusively (4dc71be)</li> <li>Ensure consistent behaviour for <code>get('name')</code> + <code>get()</code> (1953d30)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/js-cookie/js-cookie/commit/17bacba0171dd022728d8fdeba3203c60791bf58"><code>17bacba</code></a> Craft v3.0.7 release</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/adb823cb7e95ead47f3af4d4951e589acbde2077"><code>adb823c</code></a> Fix release workflow halting at <code>git tag</code></li> <li><a href="https://github.com/js-cookie/js-cookie/commit/5f9e759b07d2752e8407a3a43fb5f879bf384c5e"><code>5f9e759</code></a> May remove Git user config from release workflow</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/6ac921184c7b3b7d9431c88707f56521acd72ab4"><code>6ac9211</code></a> Fix release workflow not able to push commit + tag</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/2278bc55e1804c4c2d9bd2110a9b449949a52751"><code>2278bc5</code></a> Fix missing package version bump</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/eb3c40e89731e99b8970faaf35ddad249c6c0020"><code>eb3c40e</code></a> Prevent cookie attribute injection</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/f6f157f430d707d2ffd0c9c9138227a6cea564e5"><code>f6f157f</code></a> Bump globals from 17.5.0 to 17.6.0</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/f409d022da50a0c6fa8724f087fbc50fab9a9533"><code>f409d02</code></a> Bump eslint from 10.2.0 to 10.3.0</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/a686883c03a754c04546cfc1653911a70a640b40"><code>a686883</code></a> Bump protobufjs in the npm_and_yarn group across 1 directory</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/c6112d2d4f2881a12aaf89d9e2996ef6870eb6d0"><code>c6112d2</code></a> Bump <code>@protobufjs/utf8</code> in the npm_and_yarn group across 1 directory</li> <li>Additional commits viewable in <a href="https://github.com/js-cookie/js-cookie/compare/v3.0.5...v3.0.7">compare view</a></li> </ul> </details> <details> <summary>Maintainer changes</summary> <p>This version was pushed to npm by <a href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new releaser for js-cookie since your current version.</p> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Weiko <corentin@twenty.com> |
||
|
|
8a74ea8829 |
fix(contact-creation): enrich missing names on auto-created contacts (#21018)
## Summary
Three related fixes to the auto-creation of People records from calendar
events and email messages, all centred on the data-quality problem of
contacts being created with missing or malformed names.
### 1. Enrich names on existing contacts (commit 1)
Previously: when an email or calendar import matched an existing Person
by email, the existing record was left untouched — even if the new
source carried a better name.
This is the root cause of contacts like `"Félix"` (no last name)
sticking around forever: the `To:`/`Cc:` headers of outbound emails
rarely include a display name, and Google Calendar only returns
`displayName` for attendees already in the organizer's address book. So
the first sighting often creates a Person as `{firstName: "felix",
lastName: ""}`, and a later inbound `From: "Félix Malfait"
<felix@twenty.com>` — which would have produced the right name — gets
silently dropped because the Person already exists.
The new `computePeopleToEnrichNames` bucket and
`CreatePersonService.enrichPeopleNames` method fill in missing
`firstName`/`lastName` fields from the new parsed name, with
conservative rules:
- Only enrich when the existing Person's `createdBy.source` is
`CALENDAR` or `EMAIL` — `MANUAL`, `IMPORT`, `API`, `WORKFLOW`, etc. are
never touched.
- Only fill empty fields. Non-empty `firstName`/`lastName` are never
overwritten.
- Soft-deleted contacts continue to be handled by the existing restore
path.
### 2. Handle multi-comma "Last, First, Suffix" display names (commit 2)
The comma-inverted swap in the parser previously required *exactly* one
comma. Names like `"Smith, Jane, Jr."`, `"O'Brien, Mary, MD"` or `"Doe,
John, Patrick"` fell through to the space-split fallback, which stored
the comma in `firstName` (e.g. `"Smith,"`) and produced garbled records
(the avatar shows a single "B" and the name reads `"Barbey, Julien"`
because the entire string lives in `firstName`).
The regex now splits on the first comma and treats the remainder as the
first name, collapsing any further commas to spaces. Single-comma
behaviour is unchanged.
### 3. Perf: skip the parser when an existing record is already
populated (commit 3)
`computePeopleToEnrichNames` runs on every cron-driven email/calendar
import batch. The first version called the display-name parser for every
matched existing person, even when both `firstName` and `lastName` were
already set — i.e. the steady-state case after the initial enrichment
pass.
Reordered so the cheap "both fields populated" check short-circuits
before any parsing happens. Same behaviour, fewer parser calls on the
hot path.
## Test plan
- [x] 8 new unit tests for the enrichment bucket: empty `lastName`
enrichment, both `EMAIL` and `CALENDAR` sources, non-overwrite of
non-empty fields, skip on `MANUAL`/`IMPORT`, skip when the new source
also has no last name, skip for soft-deleted, fill `firstName` while
preserving `lastName`, handle null `name` field
- [x] 5 new parser tests for multi-comma forms: `"Last, First, Suffix"`,
credential suffixes (`MD`), three-token forms, whitespace around inner
commas, `:GROUP` tag interaction
- [x] 1 new parser test on the single-comma path covering multi-word
first names (`"Smith, Mary Jane"`)
- [x] All 15 existing parser tests still pass
- [x] All 116 tests in `contact-creation-manager` pass
- [x] `npx nx typecheck twenty-server`
- [x] `npx oxlint --type-aware` + `npx oxfmt --check` on changed files
- [ ] Manual: trigger a fresh contact creation from an outbound email
with no display name, then a subsequent inbound email from the same
address with a full display name, and confirm the Person's last name
gets populated
|
||
|
|
f4ead89956 |
refactor(twenty-orm): migrate 23 grandfathered entities to WorkspaceScopedRepository (#20987)
## Summary Follow-up to #20953. Migrates 23 of the 30 entities that were left in `WORKSPACE_SCOPED_EXEMPTIONS` last time, so the lint rule's workspaceId-enforcement default now covers most of the core/metadata schema. ### Migrated (23 entities, 88 files, 22 commits) | Family | Entities | |---|---| | Trivial caches | `NavigationMenuItem`, `Skill`, `DataSource`, `Webhook`, `CommandMenuItem`, `IndexMetadata` | | Views | `View`, `ViewField`, `ViewFieldGroup`, `ViewFilter`, `ViewFilterGroup`, `ViewGroup`, `ViewSort` | | Layouts | `PageLayout`, `PageLayoutTab`, `PageLayoutWidget` | | Roles & permissions | `Role`, `RoleTarget`, `PermissionFlag`, `ObjectPermission`, `FieldPermission`, `RowLevelPermissionPredicate`, `RowLevelPermissionPredicateGroup` | For each entity: swap `@InjectRepository(X)` → `@InjectWorkspaceScopedRepository(X)` (and the field type → `WorkspaceScopedRepository<X>`); rewrite every call site to pass `workspaceId` as the first arg (stripped from `where`/criteria — the wrapper throws if you include it now); register `provideWorkspaceScopedRepository(X)` in every owning NestJS module; update affected spec providers to `getWorkspaceScopedRepositoryToken(X)`. ### Rule update - `ApplicationRegistrationVariableEntity` was misclassified — moved to `STRUCTURAL_EXEMPTIONS` (no `workspaceId` column; it's keyed on `applicationRegistrationId` at the instance level). - 22 of the 23 migrated entities removed from `WORKSPACE_SCOPED_EXEMPTIONS` entirely (zero remaining raw `@InjectRepository` sites). - `RoleTargetEntity` also removed; one call site in `user-workspace.service.ts` keeps a raw injection with an `eslint-disable` + reason because `softRemove(...)` is not on the wrapper API yet (the migration would require threading `workspaceId` through `deleteUserWorkspace`'s three callers). ### Still exempted (7 entities, follow-up PRs) | Entity | Why deferred | |---|---| | `ApplicationEntity` | ~50 sites with several cross-workspace lookups by id (auth, OAuth, file-storage, cleanup) | | `CalendarChannelEntity` / `MessageChannelEntity` | Use `.increment(...)` (not on wrapper) and `repository.manager.transaction(...)` — wrapper needs to grow `.increment` + the transaction sites need `withManager` or dual-inject | | `FieldMetadataEntity` / `ObjectMetadataEntity` | The metadata services `extends TypeOrmQueryService<X>` and `super(rawRepo)` — requires dual-inject or reworking the inheritance | | `KeyValuePairEntity` | Allows `workspaceId: IsNull()` for instance-level config; wrapper rejects null | | `UpgradeMigrationEntity` | Same — instance-level + cross-workspace ledger | ## Test plan - [x] `npx nx typecheck twenty-server` — clean - [x] `npx nx lint twenty-server` — clean (0/0) - [x] All 10 affected unit specs pass (115 tests) — api-key, agent-role, permissions, workspace-roles-permissions-cache, view-filter-group, workflow-version-step-operations, two-factor-authentication (service + resolver), user-workspace, file - [ ] Server integration tests in CI |
||
|
|
865ca697ca |
Fix AI permission gating: use Ask AI for chat UI, AI Settings for admin endpoints (#21030)
## Summary Closes #20662. Two AI permission flags exist: - **`AI`** (label "Ask AI") — user-facing: chat with AI agents, use AI features - **`AI_SETTINGS`** (label "AI") — admin: create and configure AI agents After auditing every use of these flags I found: ### Frontend — chat UI gated by the admin permission (user-facing bug from the issue) A user granted only `Ask AI` could not see chat tabs, the "new chat" button (desktop & mobile), or the chat content pane; thread initialization was also skipped, leaving the chat in a half-initialized state and producing intermittent `THREAD_NOT_FOUND` errors. Switched these to `AI`: - `MainNavigationDrawerTabsRow.tsx` - `MainNavigationDrawer.tsx` - `MobileNavigationBar.tsx` - `AgentChatThreadInitializationEffect.tsx` ### Backend — admin-only resolvers gated by the user permission (privilege escalation) Two resolvers had a class-level guard of `AI`, letting any user with the user-facing flag reach admin endpoints (skill CRUD, eval runs). Switched the class-level guards to `AI_SETTINGS`: - `SkillResolver` — create/update/delete/activate/deactivate skills - `AgentTurnResolver` — read turns, run/grade evaluations ### Left as-is (already correct) - `AgentResolver` — class-level `AI` for reads (workflow editors and admin pages both need them), mutation-level `AI_SETTINGS` overrides for writes - `AgentChatResolver` & `AgentChatSubscriptionResolver` — already `AI` - `AiGenerateTextController` — already `AI` - Workspace AI config fields in `workspace.service.ts` — already `AI_SETTINGS` ## Test plan - [ ] As a user with `Ask AI` only (no `AI_SETTINGS`): chat tabs, "new chat" button, and chat history pane are visible on desktop + mobile; sending a message works; no `THREAD_NOT_FOUND` errors - [ ] As a user with `AI_SETTINGS` but no `Ask AI`: chat UI is hidden - [ ] As a user with `Ask AI` only: calling `skills` / `createSkill` / `agentTurns` / `runEvaluationInput` via GraphQL returns permission denied - [ ] As an admin (`AI_SETTINGS`): skill settings and agent eval pages still work |
||
|
|
ebfaca5b3d |
EncryptedString PlaintextString branded string types (#21001)
## Summary closes https://github.com/twentyhq/core-team-issues/issues/2464 Introduces compile-time branded types to distinguish encrypted ciphertext from plaintext strings, preventing mix-ups like the one fixed in #20819 — but at the type level rather in addition to the one existing at runtime. ### Branded string primitives - Created `EncryptedString` and `PlaintextString` as hard nominal brands using `z.string().brand(...)`, making them non-assignable to each other or to raw `string` - Created `isEncryptedString` type predicate to narrow `string` to `EncryptedString` based on the `enc:v2:` envelope prefix - Retyped `SecretEncryptionService`: `encryptVersioned` accepts `PlaintextString`, `decryptVersioned` returns `PlaintextString` ### Entity typing - Typed encrypted columns across entities: `SigningKeyEntity.privateKey`, `TwoFactorAuthenticationMethodEntity.secret`, `ApplicationRegistrationVariableEntity.encryptedValue`, `ApplicationVariableEntity.value` - Parameterized JSONB types for connected account connection parameters (`ImapSmtpCaldavParams<Pwd>`) with reusable aliases `EncryptedImapSmtpCaldavParams` / `DecryptedImapSmtpCaldavParams` - Typed DTOs (`CreateApplicationRegistrationVariableInput`, `UpdateApplicationRegistrationVariablePayload`, `UpdateApplicationVariableEntityInput`) with `PlaintextString` ### ApplicationVariable always-encrypt uniformization - Retyped `ApplicationVariableEntity.value` to `EncryptedString | ''` — all values are now encrypted regardless of `isSecret` - Updated `ApplicationVariableEntityService` to always encrypt on write and always decrypt on read - Simplified `UpdateApplicationVariableActionHandlerService` by removing conditional encrypt/decrypt-on-isSecret-toggle logic - Added slow instance command (`2.9.0`) to backfill-encrypt existing `isSecret=false` plaintext rows and tighten the `CHECK` constraint ### ConfigStorageService refactor - Split `convertAndSecureValue` (which used `any`) into two well-typed methods: `convertAndDecrypt` and `convertAndEncrypt` - Introduced `isSensitiveStringValue` type predicate to narrow values before encryption/decryption ### What's next - Typeorm entity derivation to strictly type sitemap configuration as code + handler logic for encryption rotation - https://github.com/twentyhq/core-team-issues/issues/2465 |
||
|
|
9b54200d8c |
Fix playwright CI (#21024)
## Context
The Install Playwright step ran npx playwright install with no
arguments, which downloads all browsers (Chromium + Firefox + WebKit +
ffmpeg, ~500MB+) on every run with no caching.
Fix:
- Install Chromium only — npx playwright install chromium instead of all
browsers.
- Cache the browser binaries — actions/cache on ~/.cache/ms-playwright,
keyed on the resolved Playwright version (v4-playwright-browsers-${{
runner.os }}-<version>). On a cache hit the install step is skipped
entirely; the cache invalidates automatically when the Playwright
version bumps.
|
||
|
|
bd6811d060 |
i18n - translations (#21022)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
b32249877a |
i18n - translations (#21021)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
6fb6ef4e7a |
Add darkmode for oAuth screen (#21005)
## after <img width="1512" height="828" alt="image" src="https://github.com/user-attachments/assets/71664eab-c921-45da-ac67-7a660c976d5c" /> |
||
|
|
bb4e28904f |
Support the "Me" filter for workspace members in dashboard widgets and add multi select (#20971)
Fixes https://github.com/twentyhq/twenty/issues/20225 The "Me" filter (current workspace member) worked in view filters but not in dashboard widget filters — the server never resolved the placeholder, and the widget side-panel UI had no "Me" option and only allowed single selection. Backend: `ChartDataQueryService` now forwards the current workspace member id (from authContext) into filterValueDependencies, so the shared filter logic resolves "Me" the same way it does for view filters. Added unit tests for the converter. Frontend: new multi-select picker for workspace member filters in the widget side panel, mirroring the view filter's actor select: search input, "Me" pinned item, and a multi-select workspace member list. ## Before <img width="3024" height="1488" alt="CleanShot 2026-05-27 at 17 16 36@2x" src="https://github.com/user-attachments/assets/b2cff46c-53e5-4e8a-a463-b106daf96c8c" /> ## After <img width="3024" height="1488" alt="CleanShot 2026-05-27 at 17 14 05@2x" src="https://github.com/user-attachments/assets/8b3b5f11-44b9-4ae5-a2f3-9c7a689f4bb2" /> |
||
|
|
c3d1af89ae |
i18n - docs translations (#21019)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
e6221c5f0e |
Fix twenty sdk billing exports (#21016)
Fix two omissions from #19973 that prevented `twenty-sdk/billing` from being a fully exported subpath: - `package.json`: add `billing` to `typesVersions` (every other subpath was listed; billing was the only one missing, breaking type resolution for consumers using classic TS moduleResolution). - `project.json`: add the billing vite build and `dist/billing` output to the `build:sdk` target |
||
|
|
6566a918af |
chore(deps): bump @apollo/client from 4.1.6 to 4.2.0 (#20993)
Bumps [@apollo/client](https://github.com/apollographql/apollo-client) from 4.1.6 to 4.2.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/apollographql/apollo-client/releases">@apollo/client's releases</a>.</em></p> <blockquote> <h2><code>@apollo/client</code><a href="https://github.com/4"><code>@4</code></a>.2.0</h2> <h3>Minor Changes</h3> <ul> <li> <p><a href="https://redirect.github.com/apollographql/apollo-client/pull/13132">#13132</a> <a href="https://github.com/apollographql/apollo-client/commit/f3ce805425d10a9666218a8e109288a2d46dcab1"><code>f3ce805</code></a> Thanks <a href="https://github.com/phryneas"><code>@phryneas</code></a>! - Introduce "classic" and "modern" method and hook signatures.</p> <p>Apollo Client 4.2 introduces two signature styles for methods and hooks. All signatures previously present are now "classic" signatures, and a new set of "modern" signatures are added alongside them.</p> <p><strong>Classic signatures</strong> are the default and are identical to the signatures before Apollo Client 4.2, preserving backward compatibility. Classic signatures still work with manually specified TypeScript generics (e.g., <code>useSuspenseQuery<MyData>(...)</code>). However, manually specifying generics has been discouraged for a long time—instead, we recommend using <code>TypedDocumentNode</code> to automatically infer types, which provides more accurate results without any manual annotations.</p> <p><strong>Modern signatures</strong> automatically incorporate your declared <code>defaultOptions</code> into return types, providing more accurate types. Modern signatures infer types from the document node and do not support manually passing generic type arguments; TypeScript will produce a type error if you attempt to do so.</p> <p>Methods and hooks automatically switch to modern signatures the moment any non-optional property is declared in <code>DeclareDefaultOptions</code>. The switch happens across all methods and hooks globally:</p> <pre lang="ts"><code>// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { namespace ApolloClient { namespace DeclareDefaultOptions { interface WatchQuery { errorPolicy: "all"; // non-optional → modern signatures activated automatically } } } } </code></pre> <p>Users can also manually switch to modern signatures without declaring any <code>defaultOptions</code>, for example when wanting accurate type inference without relying on global <code>defaultOptions</code>:</p> <pre lang="ts"><code>// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { export interface TypeOverrides { signatureStyle: "modern"; } } </code></pre> <p>Users can do a global <code>DeclareDefaultOptions</code> type augmentation and then manually switch back to "classic" for migration purposes:</p> <pre lang="ts"><code>// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { export interface TypeOverrides { signatureStyle: "classic"; } } </code></pre> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md">@apollo/client's changelog</a>.</em></p> <blockquote> <h2>4.2.0</h2> <h3>Minor Changes</h3> <ul> <li> <p><a href="https://redirect.github.com/apollographql/apollo-client/pull/13132">#13132</a> <a href="https://github.com/apollographql/apollo-client/commit/f3ce805425d10a9666218a8e109288a2d46dcab1"><code>f3ce805</code></a> Thanks <a href="https://github.com/phryneas"><code>@phryneas</code></a>! - Introduce "classic" and "modern" method and hook signatures.</p> <p>Apollo Client 4.2 introduces two signature styles for methods and hooks. All signatures previously present are now "classic" signatures, and a new set of "modern" signatures are added alongside them.</p> <p><strong>Classic signatures</strong> are the default and are identical to the signatures before Apollo Client 4.2, preserving backward compatibility. Classic signatures still work with manually specified TypeScript generics (e.g., <code>useSuspenseQuery<MyData>(...)</code>). However, manually specifying generics has been discouraged for a long time—instead, we recommend using <code>TypedDocumentNode</code> to automatically infer types, which provides more accurate results without any manual annotations.</p> <p><strong>Modern signatures</strong> automatically incorporate your declared <code>defaultOptions</code> into return types, providing more accurate types. Modern signatures infer types from the document node and do not support manually passing generic type arguments; TypeScript will produce a type error if you attempt to do so.</p> <p>Methods and hooks automatically switch to modern signatures the moment any non-optional property is declared in <code>DeclareDefaultOptions</code>. The switch happens across all methods and hooks globally:</p> <pre lang="ts"><code>// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { namespace ApolloClient { namespace DeclareDefaultOptions { interface WatchQuery { errorPolicy: "all"; // non-optional → modern signatures activated automatically } } } } </code></pre> <p>Users can also manually switch to modern signatures without declaring any <code>defaultOptions</code>, for example when wanting accurate type inference without relying on global <code>defaultOptions</code>:</p> <pre lang="ts"><code>// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { export interface TypeOverrides { signatureStyle: "modern"; } } </code></pre> <p>Users can do a global <code>DeclareDefaultOptions</code> type augmentation and then manually switch back to "classic" for migration purposes:</p> <pre lang="ts"><code>// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { export interface TypeOverrides { signatureStyle: "classic"; } } </code></pre> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/apollographql/apollo-client/commit/e010bdd239b5c10415d4b70ca791467cde12fc88"><code>e010bdd</code></a> Version Packages (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13241">#13241</a>)</li> <li><a href="https://github.com/apollographql/apollo-client/commit/9c4c01a640b43bfb47bd52b25d5881c4ad7bec71"><code>9c4c01a</code></a> Release 4.2 (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13129">#13129</a>)</li> <li><a href="https://github.com/apollographql/apollo-client/commit/222838e99bc6054120cc1f881bb225b1ef049de9"><code>222838e</code></a> Exit prerelease mode</li> <li><a href="https://github.com/apollographql/apollo-client/commit/7d3a533c811a8423536ceeebccc06413ded5b6a3"><code>7d3a533</code></a> Merge branch 'main' into release-4.2</li> <li><a href="https://github.com/apollographql/apollo-client/commit/f20d591bbf74cb4f0d87ec9a14b93a59fe46b039"><code>f20d591</code></a> chore(deps): update actions/create-github-app-token digest to d72941d (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13239">#13239</a>)</li> <li><a href="https://github.com/apollographql/apollo-client/commit/d4a28b6142e47164c8a24bd8c05a8aa3f1ce4eee"><code>d4a28b6</code></a> chore(deps): pin dependencies (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13237">#13237</a>)</li> <li><a href="https://github.com/apollographql/apollo-client/commit/c1f39cf5402b052ab92886a1857840a745aee02b"><code>c1f39cf</code></a> ci: pin Actions@SHA and disable cache on workflows with elevated OIDC permiss...</li> <li><a href="https://github.com/apollographql/apollo-client/commit/511048b7bd6253a38a6b7ebe58e9674a39c74273"><code>511048b</code></a> Event-based refetching docs (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13228">#13228</a>)</li> <li><a href="https://github.com/apollographql/apollo-client/commit/d1f68f1a5fdb7c6915a72b2426cad373a0526c06"><code>d1f68f1</code></a> Version Packages (rc) (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13234">#13234</a>)</li> <li><a href="https://github.com/apollographql/apollo-client/commit/f1b541fed4111028b6842727178288156582e669"><code>f1b541f</code></a> Prepare for rc release (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13232">#13232</a>)</li> <li>Additional commits viewable in <a href="https://github.com/apollographql/apollo-client/compare/@apollo/client@4.1.6...@apollo/client@4.2.0">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
6ea637d6c5 |
Export STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS (#21010)
fix https://discord.com/channels/1130383047699738754/1509086323464474705 |
||
|
|
531410f64a |
fix(ai): expose MORPH_RELATION join columns in AI/MCP tool schemas (#21012)
## Summary
- Fixes a bug where `noteTarget` (and any other morph-relation join
object) created via AI/MCP would land with `targetCompanyId` /
`targetPersonId` / `targetOpportunityId` left null, even though the tool
reported success.
- Root cause: the Zod schema generators for the AI tools only branched
on `FieldMetadataType.RELATION`. MORPH_RELATION fields fell through to
the default case — for `create_*` they were exposed as `targetCompany:
string` instead of `targetCompanyId: uuid`, and for `group_by_*` they
were silently skipped entirely. Downstream
(`data-arg-processor.service.ts` and the group-by arg processor) already
accept the join-column form for both kinds of relations via
`computeMorphOrRelationFieldJoinColumnName` and
`isMorphOrRelationFlatFieldMetadata`, so the fix is purely in the schema
generators.
## Changes
- `record-properties.zod-schema.ts` — extend the existing RELATION
MANY_TO_ONE / ONE_TO_MANY branches to also match MORPH_RELATION.
- `group-by-tool.zod-schema.ts` — replace the silent MORPH_RELATION skip
with the same treatment as RELATION MANY_TO_ONE (exposes `${name}Id` as
a groupBy option).
- `test/integration/ai/suites/mcp-tool-execution.integration-spec.ts` —
new file. First integration test for tool execution end-to-end. Drives
the real MCP JSON-RPC endpoint with the seeded API key (`learn_tools`
for schema introspection, `execute_tool` for invocation):
- asserts `create_note_target`'s schema exposes `targetCompanyId` /
`targetPersonId` / `targetOpportunityId` as UUIDs and does **not**
expose `targetCompany` / `targetPerson` / `targetOpportunity`.
- creates a company + note + noteTarget via MCP, then queries the
workspace schema to confirm `targetCompanyId` is actually persisted in
the FK column.
- asserts `group_by_note_targets` schema accepts `targetCompanyId` as a
groupBy key.
- sets up 3 noteTargets (2 → company A, 1 → company B), calls
`group_by_note_targets` by `targetCompanyId`, and asserts the counts.
Out of scope: `record-filter.zod-schema.ts` has the same pattern (only
RELATION) — left for a follow-up so this PR stays focused on what was
reported.
## Test plan
- [x] `npx nx typecheck twenty-server`
- [x] `npx oxlint --type-aware` on changed files — clean
- [x] `npx oxfmt --check` on changed files — clean
- [x] Integration tests pass (4/4) after `database:reset`:
- `should expose the morph-relation join columns as \`${name}Id\` UUID
parameters`
- `should persist targetCompanyId when create_note_target is invoked via
MCP`
- `should expose targetCompanyId as a valid groupBy option`
- `should group noteTargets by targetCompanyId via MCP`
|
||
|
|
182960051f |
i18n - translations (#21013)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
7d9f9605a2 |
feat(settings): move email handles and emailing domains to dedicated Email page (#21008)
## Summary Both **Email Handles** and **Emailing Domains** were rendered on the General workspace settings page, but they're workspace-level *email infrastructure* (inbound shared addresses + outbound sender authentication) and don't belong with the workspace name, picture, and domain config. - New `SettingsWorkspaceEmail` page at `/settings/email` - Nav item under **Workspace**, hidden when `IS_EMAIL_GROUP_ENABLED` is off (and gated by `WORKSPACE` permission) - Related sub-routes (`email-group/:messageChannelId`, `emailing-domain/:domainId`, etc.) moved from `general/` to `email/` so the URL space stays consistent with the page - General page now only contains name, picture, workspace domain, and the delete-workspace section No behavior changes to the underlying section components — they're imported as-is into the new page. ## Test plan - [ ] With `IS_EMAIL_GROUP_ENABLED` enabled: **Email** appears in the Workspace nav and the page renders both sections - [ ] With the flag disabled: **Email** is hidden from nav; navigating to `/settings/email` directly renders nothing - [ ] General page no longer shows Email Handles / Emailing Domains - [ ] Clicking a shared inbox row navigates to `/settings/email/email-group/:id` (was `general/...`) - [ ] "Add emailing domain" navigates to `/settings/email/emailing-domain/new` ## Notes - Pre-existing `twenty-front` typecheck error in `FrontComponentRendererProvider.tsx` (React types mismatch between sibling packages) reproduces on `main` and is unrelated to this PR. |
||
|
|
7065441972 |
fix: refresh admin panel feature flags after toggling (#21007)
## Summary
- Add `refetchQueries` to the `updateWorkspaceFeatureFlag` mutation in
the admin workspace detail page so the toggle reflects the new value
after toggling.
- Rename `useFeatureFlagState` → `useAdminUpdateFeatureFlag` since the
hook lives under `admin-panel` and is only consumed by the admin
workspace detail page.
## Bug
In the admin panel, toggling a feature flag for a workspace other than
the admin's own workspace sent the backend mutation successfully, but
the toggle in the UI remained unchanged.
The displayed value is derived from:
```tsx
const currentWorkspaceValue =
currentWorkspace?.id === workspaceId
? currentWorkspace?.featureFlags?.find((f) => f.key === flag.key)?.value
: undefined;
const displayedValue = currentWorkspaceValue ?? flag.value;
```
When viewing a different workspace, `currentWorkspaceValue` is
`undefined` so the toggle reads `flag.value` from the
`WORKSPACE_LOOKUP_ADMIN_PANEL` query. That query was never refetched
after the mutation, so the displayed value stayed stale.
The existing optimistic Jotai update on `currentWorkspaceState` still
runs — it is needed so the rest of the app (anything consuming
`useIsFeatureEnabled`) reacts immediately when an admin toggles a flag
on their own workspace.
## Test plan
- [ ] Open the admin panel → pick a workspace that is not your own →
Feature Flags tab → toggle a flag → toggle visually flips after the
mutation completes.
- [ ] Same flow on your own workspace → toggle flips, and any UI gated
on that flag also reacts.
- [ ] If the mutation fails, the toggle reverts (existing `onError`
rollback path).
|
||
|
|
6e2cf4cbc0 |
Fix lambda timeout diagnostics (#21002)
Trying to fix https://twenty-v7.sentry.io/issues/7420384466/?environment=prod&environment=prod-eu&project=4507072499810304&query=is%3Aunresolved%20%21issue.type%3A%5Bperformance_consecutive_db_queries%2Cperformance_consecutive_http%2Cperformance_file_io_main_thread%2Cperformance_db_main_thread%2Cperformance_n_plus_one_db_queries%2Cperformance_n_plus_one_api_calls%2Cperformance_p95_endpoint_regression%2Cperformance_slow_db_query%2Cperformance_render_blocking_asset_span%2Cperformance_uncompressed_assets%2Cperformance_http_overhead%2Cperformance_large_http_payload%5D%20timesSeen%3A%3E10&referrer=issue-stream&sort=date ## Summary - Prevent invoking Lambda functions stuck in `Pending` state by checking `Configuration.State === 'Active'` in `checkLambdaExecutorBuildStatus` — a Pending function now goes through `ensureLambdaExecutor` which waits for Active. - Track execution phase (`build`/`fetch-code`/`invoke`) via a `LambdaExecutionPhase` enum and include phase timing + function state in all error messages for faster debugging. Co-authored-by: Matt Van Horn <mvanhorn@users.noreply.github.com> |
||
|
|
de7daaa81a |
fix: exclude system objects and workflow/dashboard from AI/MCP write tool descriptors (#20973)
## Summary fix: exclude system join objects from AI/MCP create/update/delete tool descriptors Closes #20403 --- AI was used for assistance. --------- Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: Félix Malfait <felix@twenty.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> |
||
|
|
c0cbe67bcd |
i18n - translations (#20982)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
c5606212f2 |
Ses outbound followup (#20610)
This pull request unifies outbound with inbound under the new feature and the new email groups feature. These are workspace level shared inboxes that are shared between all workspace members. outbound sending with SES works, we only listen for tenant status events, rest is managed by AWS PR refactors old code and webhook to be split for outbound and inbound for proper separation | Area | Change | |---|---| | AWS SES driver | Split into `AwsSesRegisterDomainService` (tenant + identity + DKIM + MAIL FROM + configuration-set + EventBridge dest + contact list) and `AwsSesSendEmailService` (SendEmail). | | Reputation webhook | New `/webhooks/messaging/ses/outbound` route. SES → EventBridge (`Sending Status Enabled/Disabled` on default bus) → SNS → router → `SesOutboundSendingStateHandlerService` updates `emailing_domain.tenantStatus`. | | Inbound webhook | Refactored into `SesInboundWebhookRouterService` + `SesInboundMailHandlerService`. Shared `SnsSignatureVerifierService` + `SnsSubscriptionConfirmerService` across both routes. | | Global uniqueness | New migration + instance command: `emailing_domain.domain` is now globally unique (one tenant per domain across workspaces). | | Tenant status | New `emailing_domain.tenantStatus` column (`ACTIVE` / `PAUSED`) + `EmailingDomainTenantStatusService`. | | Send-email mutation | New `sendEmailViaDomain` GraphQL mutation + DTOs. | | Cleanup | `EmailingDomainWorkspaceCleanupJob` wired into `WorkspaceService.deleteWorkspace` — tears down SES tenant association + identity on workspace delete. | | Settings UI | Rewritten around reusable `SettingsTableListSection`. "Email Group" → "Email Handle" rename. New cells for status/source/forwarding. Outbound domains surfaced on workspace settings page. | ### Env vars (new) All in `config-variables.ts`, group `AWS_SES_SETTINGS`, all optional: - `AWS_SES_REGION` — `@IsAWSRegion`, consumed by `AwsSesClientProvider` + driver factory - `AWS_SES_ACCOUNT_ID` — used for ARN construction in driver factory - `SES_SNS_TOPIC_ARN_ALLOWLIST` — **shared** by inbound + outbound webhook routers, comma-separated list of accepted SNS topic ARNs (verified via `sns-payload-validator`) ### Migrations - `1778862608620-add-emailing-domain-tenant-status` (fast) — adds `tenantStatus` column. - `1778865501791-unique-emailing-domain-globally` (slow, idempotent) — enforces global uniqueness on `domain`. - Instance commands bumped to `2.5`. ### Infra dependency Two coupled twenty-infra PRs: - `ses-inbound-email` — receipt-rule + inbound SNS topic + S3 bucket policy + KMS grant + `email_group_*` outputs. - `ses-outbound-tf` — EventBridge rule + outbound SNS topic + SES IAM policy + outbound `webhook_url` subscription. **Based on `ses-inbound-email`.** Merge order: inbound first, then outbound. Outbound PR's chart edit owns the comma-joined `SES_SNS_TOPIC_ARN_ALLOWLIST` value (both ARNs). Features lives under `/settings/general` <img width="1496" height="845" alt="SCR-20260519-ofhi-2" src="https://github.com/user-attachments/assets/a025485a-09f7-4131-91cd-0067690ff18d" /> --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait <FelixMalfait@users.noreply.github.com> |
||
|
|
0702e72e3f |
i18n - translations (#20979)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
0503aa7982 |
Display application info in workflow side panel (#20976)
To comply this design https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=99421-167676&t=GXw0bOgp2P51YVKi-0 ## Before <img width="569" height="470" alt="image" src="https://github.com/user-attachments/assets/c52c8775-2934-4620-8885-dff5a8230a89" /> ## After <img width="314" height="258" alt="image" src="https://github.com/user-attachments/assets/df9cbd15-cf7b-4a47-ac8d-1292c71a7c94" /> |
||
|
|
f98c5d0609 |
i18n - translations (#20978)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
4797d2f270 |
feat(twenty-orm): introduce WorkspaceScopedRepository for core/metadata workspace-scoped entities (#20953)
## Summary Adds a third tenancy enforcement layer for entities that live in shared schemas (`core`, `metadata`) and carry a `workspaceId` column — previously the only safeguard at this layer was developer discipline (remembering to put `workspaceId` in every WHERE clause). ### The three layers, after this PR | Layer | Scope | How it's enforced | |---|---|---| | 1. Workspace data | per-workspace schema (companies, people, custom objects) | `twentyORMManager.getRepository(workspace, E)` — physical isolation (own data source) | | 2. Metadata | shared `metadata` schema (objectMetadata, fieldMetadata, views, roles…) | Flat-entity-maps cache — workspace-scoped in-memory map, lookups by id within it | | 3. Core (new) | shared `core` schema (agent threads/turns/messages, app tokens, etc.) | `WorkspaceScopedRepository<T>` — `workspaceId` is a required positional argument on every read/write | ## What's in the PR ### The wrapper (`packages/twenty-server/src/engine/twenty-orm/workspace-scoped-repository/`) - `WorkspaceScopedRepository<T extends WorkspaceScopedEntity>` — wraps a TypeORM `Repository<T>`, requires `workspaceId` on every `find`/`findOne`/`findOneOrFail`/`update`/`delete`/`softDelete`/`insert`/`save`/`count` call, merging it into the WHERE or stamping it on the entity. `createQueryBuilder` is an explicit escape hatch (caller scopes manually). - Provided via Nest DI with `@InjectWorkspaceScopedRepository(EntityClass)` and the `provideWorkspaceScopedRepository(EntityClass)` provider factory. - 19 unit tests cover the merge behavior, override-on-conflict, and the array-where (OR) case. ### Lint enforcement (`packages/twenty-oxlint-rules/rules/prefer-workspace-scoped-repository.ts`) - New `twenty/prefer-workspace-scoped-repository` rule (level: **error**). - Blacklist of entity names: raw `@InjectRepository(E)` is rejected if `E` is on the list. - Initial list: `AgentTurnEntity`, `AgentMessageEntity`, `AgentMessagePartEntity`, `AgentChatThreadEntity`, `AgentTurnEvaluationEntity`, `AgentEntity`. - Designed to grow over time as more consumers are migrated. - 5 rule tests. ### Migration in this PR All consumers of the six blacklisted entities, including: - AI agent / chat / monitor resolvers, services, and jobs - `AgentService`, `AiAgentRoleService`, `AiAgentWorkflowAction`, `ApplicationService`, `WorkspaceFlatAgentMapCacheService` - Admin-panel chat (migrated where the lookup is workspace-known; one documented `eslint-disable` on the threadId-discovery lookup that necessarily precedes the `allowImpersonation` permission check) - `AiAgentRoleService` unit spec updated to mock the scoped wrapper ## Future work (deliberately not in this PR) A standalone audit identified ~14 additional `core`/`metadata` entities with `workspaceId` that currently use raw `@InjectRepository` and could be added to the blacklist. Notable candidates: `UserWorkspaceEntity` (42 sites), `AppTokenEntity` (10), `FileEntity` (7), `BillingCustomerEntity`/`BillingSubscriptionEntity` (~22 combined). Each should be its own PR — the migration is mechanical but the surface is wide. ## Test plan - [x] `npx nx typecheck twenty-server` — clean - [x] `npx nx lint twenty-server` — 0 warnings, 0 errors - [x] `npx jest workspace-scoped-repository` — 19/19 pass - [x] `npx nx test twenty-oxlint-rules` — 215/215 pass - [x] `npx jest src/engine/metadata-modules/ai` — 44/44 pass - [ ] Manual smoke: end-to-end AI agent chat send/receive (reviewer) - [ ] Manual smoke: AI agent monitor — list turns, run evaluation (reviewer) - [ ] Manual smoke: admin-panel chat thread inspection (reviewer) |
||
|
|
c8b9dace72 |
Fix focus in front components inputs (#20961)
Fixes https://github.com/twentyhq/twenty/issues/20714 Fixes keyboard hotkey conflicts when typing inside `<input>` / `<textarea>` elements rendered by Front Components. Editable fields rendered through the component renderer now properly push/pop a focus item onto Twenty's focus stack, disabling global keyboard hotkeys while the user is typing. ## Before https://github.com/user-attachments/assets/2003c2cb-2698-480f-aedf-bb2f30396572 ## After https://github.com/user-attachments/assets/2c7c6cb0-ecd7-4557-a77b-4d1f264345f0 |
||
|
|
fbae66de8a |
Fix error when token invalid (#20972)
## Before <img width="914" height="519" alt="image" src="https://github.com/user-attachments/assets/8933bf9e-d8db-4670-ad08-69e900083fc1" /> ## After <img width="1105" height="523" alt="image" src="https://github.com/user-attachments/assets/d8638dbb-adee-4b7d-9d29-1a2a0185ab4f" /> |
||
|
|
46e7f23df1 |
fix(contact-creation): handle common email display-name shapes when auto-creating People (#20639)
## Summary
When messages are imported, Twenty auto-creates a Person record for any
recipient that doesn't exist yet. The display-name parser used at that
point is `displayName.split(' ')[0] / [1]`, which silently mangles
several common header shapes:
| Header | Old result |
|-------------------------------------------------|-----------------------------------------|
| `"Doe, John" <...>` | `firstName="Doe,"`, `lastName="John"` |
| `"John.Doe Doe" <...>` | `firstName="John.Doe"`, `lastName="Doe"`|
| `"Mary Jane Watson" <...>` | `lastName="Jane"` ("Watson" dropped) |
| `"john.doe@x.com" <john.doe@x.com>` (forwarder) | full address in
`firstName` |
| `"Doe, John:GROUP" <...>` (group-tag servers) |
`firstName="John:GROUP"` |
This PR rewrites `getFirstNameAndLastNameFromHandleAndDisplayName` to
handle each pattern. Behaviour in order:
1. Trim + strip wrapping quotes
2. Swap `"Last, First"` comma form
3. Fall back to handle parsing when display name contains `@` (real
names don't)
4. Split single dotted tokens (`"john.doe"` → `"John"`, `"Doe"`)
5. Preserve multi-word last names (`tokens.slice(1).join(' ')`)
6. De-synthesize dot-glued first names (`"John.Doe Doe"` → `"John"`,
`"Doe"`)
7. Strip `:XXX` trailing tag suffix from each parsed field
## Test plan
- [x] 16 new unit test cases covering each shape
(`__tests__/get-first-name-and-last-name-from-handle-and-display-name.util.spec.ts`)
- [x] Lint + typecheck clean
- [ ] No regression in the messaging import flow
---------
Co-authored-by: neo773 <62795688+neo773@users.noreply.github.com>
Co-authored-by: neo773 <neo773@protonmail.com>
|
||
|
|
6f3541fd7c |
i18n - translations (#20975)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
863f3f29a2 |
Fix page layout widget tab moves (#20915)
Fixes widget moves between page layout tabs by making pageLayoutTabId part of the flat-entity diff, so the save mutation no longer silently drops the new tab assignment. https://discord.com/channels/1130383047699738754/1508737039128985680 |
||
|
|
9264480824 |
Display which remote is used when twenty-sdk runs command (#20969)
## After <img width="669" height="186" alt="image" src="https://github.com/user-attachments/assets/c6ce25c9-35a9-4b2f-a473-59803b8931cc" /> |
||
|
|
5ff8c3b219 |
revert(navigation-drawer): unwanted desktop design changes from #20634 (#20955)
Follow-up to #20634. Removes three desktop design changes that bled in unintentionally: - `font-weight: regular` → restored to `medium` on nav item labels - `MenuItemIconBoxContainer` wrap around bare icons → removed - Section title `padding-right/top` tweak → restored to original values Mobile-specific fixes from #20634 (slide-over drawer width, min-width overflow fixes, breadcrumb cleanup, etc.) are preserved. |