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>
1. tool-registry.service.ts, Pass precomputed catalog to
resolveSchemas()
resolveSchemas() now accepts an optional precomputedCatalog parameter.
Both getToolsByName() and getToolInfo() pass the catalog they already
fetched, eliminating a redundant getCatalog() rebuild inside
resolveSchemas().
2. database-tool.provider.ts, Skip field lookup when schemas=false
When building the catalog index (includeSchemas=false),
getFlatFieldsFromFlatObjectMetadata() is no longer called for each of
the 25 objects. The hasGroupByToolInputSchema() check is also skipped,
group_by tools are always included in the index, with the real
eligibility check deferred to learn_tools time.
--> 100/150ms gain on learn/execute_tool execution
# Introduction
Atm when computing the `fromAllFlatEntityMaps` we're retrieving all the
applicationIds related metadata entities to build a flat entity maps
scoped to them ( atm always the applicationId + twenty-standard
application id ) only inter app dependency we manage for the moment
A flat entity contains universal identifier aggregator to its related
entities
The issue was that the `getApplicationSubAllFlatEntityMaps` wasn't
pruning the aggregator by app
Now added a new process phase after the initial one that will check that
all the aggregators contains universal identifiers that has been
retrieve from the appId + appId standard intersection
## TDD test
Created a very human readable ( that's a joke ) test
## Summary
- Fixes a regression from #21176 where activating a workflow caused it
to disappear until page refresh
- Root cause: when a draft is activated (status DRAFT→ACTIVE),
`useEffectiveDraftVersionId` incorrectly treated it as a discard because
the cached version was no longer DRAFT, filtering it from the versions
list
- Fix: only set `lastDiscardedDraftId` when `deletedAt` is actually set
on the cached version, not when the status simply changes
## Test plan
- [x] Open a workflow with a DRAFT version
- [x] Activate the workflow → verify it does NOT disappear
- [x] Discard a draft → verify header does NOT flicker between
DRAFT/ACTIVE
<img width="948" height="593" alt="image"
src="https://github.com/user-attachments/assets/d990fa98-3cfd-469d-ab7f-0b2d4ccf3afc"
/>
<img width="1361" height="802" alt="image"
src="https://github.com/user-attachments/assets/1091f598-49f3-4c16-92ea-1e1c200181e2"
/>
## Add `runAgent()` to the Logic Function SDK
Lets an app's logic function run one of its own AI agents server-side
and get the result back synchronously — reusing the existing agent
executor instead of a new bespoke transport.
### Backend
- New **`runAgent` GraphQL mutation** (metadata schema) in
`ai-agent-execution`, wrapping the existing
`AgentAsyncExecutorService.executeAgent`. Scopes the agent lookup to the
calling
application and runs it under an application auth context.
- New `@AuthApplication()` param decorator (mirrors `@AuthWorkspace()`)
— first GraphQL resolver authenticated by an **application access
token**.
- Guarded by `WorkspaceAuthGuard` +
`SettingsPermissionGuard(PermissionFlagType.AI)`: the app's role must
grant the `AI` permission flag.
### SDK
- `runAgent({ agentUniversalIdentifier, prompt })` posts the mutation to
`/metadata` with the app token via a new runtime GraphQL transport.
Returns `{ result, hasNoMoreAvailableCredits
}`.
- Refactored the connections helpers onto a shared `postAppEndpoint`
util (removes duplicated transport logic).
### Frontend
- App install permission modal now shows an explicit consent line —
_"Run AI agents and bill AI credits to your workspace"_ — when the app's
role requests the `AI` flag.
### Docs
- Documented `runAgent` and its `AI` permission-flag requirement in
_Skills & Agents_.
- Fixed outdated role-permission examples in _Roles & Permissions_
(`permissionFlags` → `permissionFlagUniversalIdentifiers`,
`PermissionFlag` → `SystemPermissionFlag`).
### Test plan
- [x] SDK unit tests (`run-agent.spec.ts`) — request shape, GraphQL/HTTP
error handling, missing env vars
- [x] `twenty-server`, `twenty-front`, `twenty-shared` typecheck + lint
- [ ] Manual: install an app granting the `AI` flag, call `runAgent()`
from a logic function, confirm the agent runs and credits are billed
---------
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
# Scaffold People Data Labs enrichment app
Defines the data model for enriching **Person** and **Company** with
People Data
Labs data. **Scaffold only** — the enrichment logic (the "mapper")
follows
separately; see the package README.
## Included
- **Fields** on Person & Company (PDL base data set).
- **Enums as SELECT / MULTI_SELECT** validated against PDL canonical
files (v34.1).
- **Standard-field mapping**: no `pdl*` shadow where a standard field
exists.
- **Location → ADDRESS**; **relation** `pdlCurrentCompany` ↔
`pdlCurrentEmployees`.
- **Metadata**: `pdlId`, `pdlLikelihood`, `pdlEnrichmentStatus`,
`pdlLastEnrichedAt`, `pdlRawPayload`.
- Shared option constants + helper, indexes, and a view per object.
# Migrate Company and Person standard fields in preparation for the
enrichment app
## Why
Our standard `Person`/`Company` objects accumulated fields that aren't
generic to every
business, while missing a more universal revenue field that essentially
every CRM ships.
This PR makes the **Standard application** hold a tighter, more
universal set of fields,
and sets the stage for a follow-up PR that introduces a **People Data
Labs enrichment app**
to populate them.
## What changes
### Standard fields
**Demoted (Standard → Workspace Custom application)** — not generic
enough to ship as standard:
| Object | Field | Type |
| ------- | ------------------------------ | -------- |
| Company | annualRecurringRevenue (ARR) | CURRENCY |
| Company | employees | NUMBER |
| Company | idealCustomerProfile (ICP) | BOOLEAN |
| Company | xLink (X/Twitter) | LINKS |
| Person | xLink (X/Twitter) | LINKS |
| Person | city | TEXT |
**Added (new generic Standard field)** — present in
Salesforce/HubSpot/Zoho, PDL-populatable:
| Object | Field | Type |
| ------- | ------------- |
-------------------------------------------------------- |
| Company | annualRevenue | CURRENCY (generic total revenue; replaces
the niche ARR) |
### Behavior by workspace
* **New workspaces:** demoted fields are gone; `annualRevenue` is
**active**.
* **Existing workspaces:** demoted fields are **preserved as active
custom fields, data intact**;
`annualRevenue` is created **inactive (opt-in)** with its column ready,
so a later activation
is a metadata-only toggle.
### Upgrade commands (v2.9)
Three idempotent, per-workspace commands, run in timestamp order:
1. **`upgrade:2-9:move-demoted-standard-fields-to-custom-application`**
(1799000040000) —
re-owns the 6 demoted fields to the workspace custom application
(`isCustom = true`,
new `applicationId` + fresh `universalIdentifier`), keeping their data
and active state.
2. **`upgrade:2-9:rename-conflicting-custom-fields`** (1799000045000) —
if a workspace already
has a *custom* field named `annualRevenue`, renames it to
`annualRevenueCustom`
(data preserved via column rename) so the standard field can be added.
Skips non-custom matches.
3. **`upgrade:2-9:add-inactive-generic-standard-fields`**
(1799000050000) — creates
`Company.annualRevenue` on existing workspaces as inactive, guarded to
skip workspaces
missing the target object or where the name is still taken.
**Failure model:** the workspace iterator isolates failures per
workspace (one workspace failing
never affects others); within a workspace the runner records per-command
status and resumes on the
next run, and every command is idempotent, so partial runs self-heal.
### Supporting changes
* **Field-option color palette:** widened the `TagColor` union
(`twenty-shared` `FieldMetadataOptions`
+ the field-metadata `options.input` DTO) from 10 colors to the full
theme palette, benefiting any
future SELECT/MULTI_SELECT field.
* **Dev seeder:**
* The default "Annual Recurring Revenue" dashboard widget now points at
the generic
`annualRevenue` field (renamed to "Annual Revenue").
* Removed the "Companies by Size (Stacked by City)" widget (relied on
the demoted `employees`).
* `employees` is dropped from company data seeds and re-added as a
**custom** field seed, so dev
workspaces still get an `employees` column matching the demoted
behavior.
### Cleanup
Front-end record types (`Company.ts`/`Person.ts`), the
`getDisplayNameFromParticipant` test mock,
metadata integration specs, the Zapier `crud_record` test, and the
regenerated
`get-standard-object-metadata-related-entity-ids` snapshot.
## ⚠️ Breaking change (intentional)
Removes standard fields `Company.annualRecurringRevenue`,
`Company.employees`,
`Company.idealCustomerProfile`, `Company.xLink`, `Person.xLink`, and
`Person.city` from the core
GraphQL schema (replaced by `Company.annualRevenue`).
This is why the breaking-changes check reports a large number of
removals — `graphql-inspector`
flags any removed object field plus its derived
aggregate/order-by/filter/update types.
**Mitigation:** the
`upgrade:2-9:move-demoted-standard-fields-to-custom-application` command
re-owns these fields as custom fields per workspace, preserving their
name and data, so existing
tenants keep working. New workspaces won't have them.
Our scanner flags the `dangerouslySetInnerHTML` in `JsonLd.tsx` as a
potential XSS sink. Since JSON-LD must be emitted as raw `<script
type="application/ld+json">` text (rendering it as a React child
HTML-entity-escapes it and corrupts the JSON, and the site is statically
generated so it must be in the SSG HTML for crawlers),
`dangerouslySetInnerHTML` is the correct, Next.js-documented approach
(the real fix is sanitizing the payload). This PR swaps our hand-rolled
`JSON.stringify().replace(/</g, ...)` for
[`serialize-javascript`](https://www.npmjs.com/package/serialize-javascript)
in `isJSON` mode, the maintained library [Next.js explicitly
recommends](https://nextjs.org/docs/app/guides/json-ld) for this, so the
script-unsafe characters are escaped by a vetted serializer rather than
custom code.
## Summary
- **Username-prefix branches**: Local visual-diff builds now use
`charles/main` instead of `main` as the branch name, preventing local
runs from creating auto-approved reference builds that could overwrite
CI baselines.
- **Local merge-base computation**: Computes `ARGOS_REFERENCE_COMMIT`
via `git merge-base HEAD main` locally, so the Argos SDK skips `git
fetch origin <branch>` — fixing the "fatal: couldn't find remote ref"
error when running from non-pushed branches.
- **Pass `referenceCommit` to vitest plugin**: Ensures the locally
computed merge-base is forwarded to the Argos upload.
## Test plan
- [x] Verified local visual-diff works from `main` branch (branch
becomes `charles/main`, not auto-approved)
- [x] Verified local visual-diff works from a non-pushed branch
(`test/local-only-visual-diff` → build uploaded successfully)
## Summary
- Remove `chromatic` and `@chromatic-com/storybook` devDependencies from
twenty-front
- Remove global `chromatic` Nx target from nx.json and twenty-front
project.json override
- Remove commented Chromatic Storybook addon from twenty-front
- Remove `CHROMATIC_PROJECT_TOKEN` from .env.example
- Update README to remove Chromatic sponsor reference (image was already
missing)
- Update stale Chromatic comment in toSpliced.ts
## Context
Visual regression testing has moved from Chromatic SaaS to self-hosted
Argos at `argos.twenty-internal.com`. These are dead references that are
no longer used by any CI workflow.
**Note:** Story `parameters.chromatic: { disableSnapshot: true }`
entries are intentionally kept — the Argos plugin reads them as a
fallback.
## Test plan
- Verify `yarn install` succeeds after dependency removal
- Verify no workflow references `chromatic` or `nx chromatic`
## Summary
- Add explicit `if: always() && needs.ui-sb-build.result == 'success'`
to `ui-sb-test` job
## Context
After merging #21217, the main-branch Argos baseline pipeline doesn't
work: `ui-sb-test` is silently skipped on push to main, so no
`argos-screenshots-twenty-ui` artifact is produced.
**Root cause:** `changed-files-check` is skipped on push events
(PR-only). `ui-sb-build` handles this with `if: always() && ...`, but
`ui-sb-test` has no explicit `if` — GitHub Actions propagates the skip
through the transitive dependency chain (`changed-files-check` →
`ui-sb-build` → `ui-sb-test`).
## Test plan
- Merge this PR and verify the next push to main produces the
`argos-screenshots-twenty-ui` artifact
- Verify `dispatch-main` successfully triggers ci-privileged with the
artifact
## Summary
**CI: Main-branch Argos baselines**
- Run storybook build + screenshot capture on `push` to `main` in CI UI
workflow
- Add `dispatch-main` job in visual regression dispatch to forward
main-branch screenshots to ci-privileged
- Simplify `dispatch-pr` by inlining the artifact name and removing
unused `project` output
**Local visual diff support**
- Add `scripts/visual-diff.sh` for running Argos uploads locally via
tunnel
- Add `storybook:visual-diff` Nx target wrapping the script (depends on
`storybook:build`)
- Honor `STORYBOOK_URL` env in `vitest.config.ts` to reuse pre-served
static builds (mirrors twenty-front pattern)
- Support `ARGOS_BUILD_NAME`, `ARGOS_REFERENCE_BRANCH` env overrides in
vitest plugin config
## Context
Argos builds on PRs are all "Orphan" because there's no reference build
on `main` to compare against. The CI changes add the missing piece:
every merge to main now produces screenshots and uploads them to Argos
as reference builds.
The local visual diff script enables developers to run visual regression
checks from their machine against the self-hosted Argos instance via
`kubectl port-forward` (set up by the twenty-infra `argos-tunnel`
command).
## Related
- twentyhq/twenty-argos#1 (backend config for self-hosted HTTPS
redirect)
- twentyhq/twenty-infra#709 (argos-tunnel super CLI command +
self-hosted mode)
## Test plan
- [ ] Verify CI UI runs on next push to main and produces the
`argos-screenshots-twenty-ui` artifact
- [ ] Verify `dispatch-main` triggers and uploads screenshots to Argos
- [ ] Verify subsequent PR builds show diffs against the main baseline
instead of "Orphan"
- [ ] Run `ARGOS_TOKEN=<token> npx nx storybook:visual-diff twenty-ui`
locally with tunnel active
some synced messages were stored with empty bodies, others with the
entire reply thread re-quoted, planer was stripping entirely quoted
forwards down to nothing and not trimming inline reply history at all
switched plaintext quote stripping to `email-reply-parser`, falling back
to the full text when it strips everything so forwards don't end up
blank. kept planer for the html path, and normalized body whitespac
---------
Co-authored-by: prastoin <paul@twenty.com>
## Summary
- Adds a standalone `/partners/apply` page — a shareable URL that opens
the partner application wizard full-page (no modal, no nav, no footer),
on a plain black background
- Adds a `slots` prop to `PartnerApplicationWizard` so it can render
outside a `Dialog.Root` context (Base UI), keeping the existing modal on
`/partners` completely untouched
- Surfaces logic function errors to the user: the API route now checks
the webhook response body for `ok: true`, so a silent backend failure no
longer shows a false success state
## Test plan
- [ ] `yarn jest --no-coverage` — 365/365 passing
- [ ] `npx oxlint -c .oxlintrc.json .` — 0 errors (1 pre-existing
warning unrelated to this PR)
- [ ] `npx oxfmt --check .` — clean
- [ ] `npx tsc --noEmit` — clean
- [ ] Visit `/partners/apply` — wizard loads full-page on black
background, no menu, no footer
- [ ] Complete the wizard and submit — redirects to `/partners/list`
- [ ] Visit `/partners` — "Become a partner" modal still opens normally
## Summary
- Moves current version to previous versions array
- Sets TWENTY_CURRENT_VERSION to the new version
- Updates TWENTY_NEXT_VERSIONS with the next minor version
- Bumps twenty-client-sdk, twenty-sdk, and create-twenty-app to the same
version
## Checklist
- [ ] Verify version constants are correct
- [ ] Verify npm package versions match
Co-authored-by: Github Action Deploy <github-action-deploy@twenty.com>
## Summary
- Only trigger visual regression on `CI UI` workflow (drop `CI Front`)
- Remove tarball re-packaging step — `ci-privileged` now downloads the
artifact directly via GitHub API
- Remove `mode`/`project` parameters from the dispatch payload
(hardcoded to twenty-ui in ci-privileged)
- Pass `run_id` of the triggering CI UI workflow so ci-privileged can
fetch the correct artifact
## Context
Part of the fast visual regression CI initiative. The `ci-privileged`
workflow has been simplified to only handle `twenty-ui` screenshots
uploaded directly to Argos.
## Test plan
- [x] Full E2E verified on production: screenshots → Argos build → diff
results → PR comment
Bumps
[@types/passport-microsoft](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/passport-microsoft)
from 2.1.0 to 2.1.1.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/passport-microsoft">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>
Three small post-redesign UI fixes. Each is an independent commit, so
they can be split into separate PRs if preferred.
## 1. Settings loading skeleton — match the rounded-card layout
The redesign (#21131) moved settings chrome into a rounded card
(`SettingsPageLayout`: bordered header with breadcrumb + centered title,
optional secondary bar, 760px body), but `SettingsSkeletonLoader` still
rendered the old flat `PageHeader` + `PageBody` — so pages painted as a
full-width flat bar then snapped into the card.
- `SettingsSkeletonLoader` now reproduces the card and **reuses the real
`SettingsPageHeader` + `SettingsPageContainer`**, so the frame aligns by
construction; the card CSS is replicated (not `SettingsPageLayout`) to
avoid the layout's side effects (hotkeys, side panel, info banner).
- It's **composed with `SettingsSectionSkeletonLoader`** so the loading
body is identical whether or not chrome is present. Rule: no chrome on
screen yet → full-page skeleton; chrome already on screen → body-only
`SettingsSectionSkeletonLoader` (the admin Enterprise tab now uses it,
matching its sibling tabs). A short comment on each component documents
this.
## 2. Application detail header — pass a plain title
`SettingsApplicationDetails` / `SettingsAvailableApplicationDetails`
passed a custom `SettingsApplicationDetailTitle` (avatar + name +
multi-line description, fixed width) into `SettingsPageLayout`'s
**centered single-line title slot**, which broke the header. They now
pass the app's display name like every other page. The available-app
"unlisted" notice moves into the body as a reusable `InlineBanner`; the
now-unused `SettingsApplicationDetailTitle` is removed.
## 3. Navigation — hide Favorites when empty
Always rendering the Favorites section (#21087) left a stray "Favorites"
title above Workspace for users with no favorites. It now renders only
when at least one favorite exists (redundant per-child guards dropped).
Note: the "+ add favorite" entry point therefore appears once you have
≥1 favorite; the first favorite is created from a record/view as before.
## Verification
- `nx typecheck twenty-front` ✅ · `oxlint` + `oxfmt --check` on changed
files ✅
- i18n catalogs intentionally untouched — handled by the repo's separate
i18n pipeline.
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>
## What & why
The audit-log viewer lived as a full-screen page reachable only via a
"View Logs" button buried in the **Security** tab. This surfaces it as
the **third tab in General settings** (`General | Security | Logs`),
consistent with the other tabs.
## Changes
- **Relocated** the event-logs module
`pages/settings/security/event-logs/` → `modules/settings/event-logs/`
and render it as tab content instead of a `FullScreenContainer` page.
Dropped `SettingsPath.EventLogs`, its route, and the fullscreen handling
in favor of the `general#logs` hash tab.
- **Security tab:** removed the "View Logs" entry; kept the
log-retention setting there.
- **In-tab gating** (shown to users with the Security permission):
Enterprise upgrade card when not entitled, a clear "ClickHouse not
configured" placeholder otherwise (derived from client config), and the
query is skipped when disabled. Replaces a bespoke error component that
string-matched error messages with the shared `SettingsEmptyPlaceholder`
/ `SettingsEnterpriseFeatureGateCard`.
- **Layout:** boxed content column with the table selector + filters
grouped in a `Card` and the results table below, matching settings
conventions. Kept the existing fixed filters (page/event name, member,
period) rather than recreating the record-view filter chips (those are
tightly coupled to record/view context).
Frontend + `twenty-shared` only — no changes to the log query or data.
## Test plan
- [x] `npx nx typecheck twenty-front` and `npx nx lint twenty-front`
pass
- [x] Settings → General shows three tabs; Logs is the third; breadcrumb
stays "Workspace / General"
- [x] With Enterprise + ClickHouse: table selector, filters, refresh,
and the paginated table work
- [x] Non-Enterprise: Enterprise upgrade card shown; no failing query
fires
- [ ] Enterprise without ClickHouse: shows the "ClickHouse not
configured" placeholder
- [ ] Security tab still shows the log-retention setting and the "View
Logs" button is gone
- [ ] A user without the Security permission sees neither the Security
nor Logs tab
## Summary
- Adds `@argos-ci/storybook` vitest plugin to `twenty-ui` for automatic
screenshot capture during vitest storybook tests
- Uploads captured screenshots (PNG, ~5MB) as a CI artifact instead of
passing the full storybook build
- Updates the visual regression dispatch workflow to pass
`mode=argos-screenshots` to ci-privileged, which then uploads
screenshots to Argos via CLI
This replaces the 10-minute Storybook screenshot capture with a ~30s
vitest browser-mode approach. The heavy screenshot work happens on free
public runners, while ci-privileged only handles the Argos API upload
(keeping secrets private).
## Architecture
```
twenty (public, free runners) ci-privileged (private)
───────────────────────────── ────────────────────────
1. Build storybook-static 4. Download screenshots artifact
2. Vitest captures screenshots 5. `argos upload` → Argos API
3. Upload screenshots artifact 6. Poll for results
7. Post PR comment
```
## Test plan
- [x] Verified locally: vitest captures 225 screenshots in ~28s
- [x] Verified `@argos-ci/cli upload` successfully creates Argos build
from captured screenshots
- [x] Argos diffs computed and results visible via API
- [ ] CI runs end-to-end on a PR