e515f174a437cfdca47d9bf561fbcce361ce0366
170 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5cb8a091fc |
Add Recall webhook status handler to the meeting bot app (#21659)
Adds a `recall-webhook` logic function (`POST /webhook/recall`, unauthenticated) to the `twenty-meeting-bot` app. It verifies the Recall/Svix `whsec_` signature over the raw body, parses bot lifecycle events, matches the corresponding `CallRecording` (by `twentyCallRecordingId` metadata, falling back to `externalBotId`), and updates lifecycle fields — `status`, `externalBotId`, `externalRecordingId`, and `startedAt`/`endedAt` (only when unset) — guarded against stale out-of-order events that would move the status backwards. Adds the required `RECALL_WEBHOOK_SECRET` server variable. This opens the real provider test path: install the app → schedule a bot through the existing calendar-event flow → point a Recall webhook endpoint at Twenty → bot lifecycle events update the matching `CallRecording`. Unit tests cover signature verification, status mapping, the downgrade guard, metadata/bot-id matching, and timestamp fill. Deferred to later PRs: - transcript/media ingestion, file uploads, and the completion charge (so `COMPLETED` is never set here) - repair/reconcile cron jobs Also flips `DEFAULT_RECALL_REGION` to `eu-central-1` (separate commit) to match the Recall account region. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21659?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. --> |
||
|
|
e83fa2108d |
Add single-record People Data Labs enrich functions (company & person) (#21650)
Adds two single-record enrichment logic functions to the People Data
Labs app — `enrich-company` and `enrich-person` — that call PDL's
single-record Enrichment endpoints (`/company/enrich`,
`/person/enrich`). Each function declares both a workflow-action trigger
and an AI-tool trigger, so the same function is usable as a workflow
step and as an AI tool. They take a single `{ recordId,
overrideExistingValues? }` and return a single `EnrichResult`.
The new functions replace the previous `enrich-company-tool` /
`enrich-person-tool` AI-tool functions (which delegated to the bulk
endpoints), avoiding duplicate near-identical tools for the LLM. The
bulk `enrich-companies` / `enrich-people` workflow actions are
unchanged.
Implementation reuses the existing enrichment machinery: the
single-record adapters spread the existing company/person adapters and
only override `enrichBatch`, so identifier extraction, TTL guard, field
mapping (fill-only-if-empty), billing, and error backoff all carry over.
A new `post-pdl-single-enrich` util posts params directly and classifies
the response via the existing `parsePdlItem`.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21650?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. -->
|
||
|
|
12b1dba986 |
Add call recording scheduling backend (#21629)
This PR adds the backend scheduling slice for call recording. It wires the `twenty-meeting-bot` internal app to reconcile calendar events, calendar-channel associations, and workspace member auto-record preference changes, then schedule, cancel, or reschedule Recall bots based on the resulting policy. It also adds the needed calendar-channel owner lookup support, generated metadata updates, app config/default role updates, unit tests, and CI for the internal app. Coming next: - Recall webhook handling and signature validation - Stale-state convergence for failed Recall cleanup/recreate cases - Media, transcript, audio, and video ingestion - Billing charge flow - Frontend/settings UI for recording controls <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21629?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. --> |
||
|
|
065b6efe11 |
fix(twenty-partners): reuse existing company by domain in partner-application handler (#21615)
## Problem
Partner applications **502** for any applicant whose company is already
in the CRM.
The `submit-partner-application` logic function dedupes applicants
**only by person email**. When no person matches that email, it takes
the create path and calls `createCompany` unconditionally. But
`Company.domainName` has a **UNIQUE index**, so whenever a company with
the applicant's domain already exists — which is common, since the **TFT
import seeds companies** — the mutation throws `"duplicate entry"`. The
handler's `catch` returns `{ ok: false }`, and the website
`/api/partner-application` route surfaces it as a **502**. The applicant
can never be submitted.
Real case that surfaced this: an applicant whose company (`BKG
Integration UG`, domain `bkg-integration.de`) was already present from
the TFT import with no Partner/Person attached.
## Fix
Extract `findOrCreateCompanyId`:
- Look the company up by **exact domain** (`domainName.primaryLinkUrl
eq`) and **reuse** it when found.
- Only `createCompany` when no domain matches.
- The matched company is **never renamed** — the existing CRM name wins
over the applicant's free-text `companyName`.
Person-email dedup is unchanged (already handled upstream in the
handler).
### Known limitation
Matches **active** rows only. A *soft-deleted* company still holds the
unique index and would re-collide; clear those with `yarn purge:prod`.
Noted inline.
## Tests
Adds an integration test: pre-seed a company by domain → submit an
application with the same domain → assert the partner reuses the same
company id and the company name is untouched.
## Version
`twenty-partners` 0.5.1 → **0.5.2** (patch: bug fix, no schema change).
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21615?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. -->
|
||
|
|
fdab89ae02 |
Move twenty-client-sdk to dev dep (#21611)
# Introduction The `twenty-client-sdk` is always provided and injected at runtime by the twenty-server instance Which mean that even if in your app locally you're using twenty-client-sdk `1.0` installing this app on twenty instance `2.0` will result in injecting another `twenty-client-sdk` That's the expected behavior and tradeof The twenty-app devdep should only be used to guide local devxp following typesafety and so on A user can still locally generated its own twenty-client-sdk and publish it if necessary <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21611?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. --> |
||
|
|
09694b2f3b |
feat(partners): add twenty-partner-match skill (#21601)
## Summary - Adds `twenty-partner-match` — a Claude Code skill that closes the partner pipeline loop: query validated partners from the API, score and explain candidates against a lead's match criteria, pause for human validation, then generate and open all intro emails in Gmail (1 client notification + 2N partner emails for N confirmed partners) - Updates `twenty-partner-design-doc` to distinguish **default zero-inference mode** (strict, 1-page brief) from `--full` inference mode, and adds **Step 8** which always produces `partner-match-criteria.md` alongside the brief - Updates `design-doc-doctrine.md` with the full zero-inference / full-mode doctrine so the Claude Code skill and a future in-product `defineSkill` stay in sync ## Skill chain ``` /twenty-lead-intro-call-summary → /twenty-partner-design-doc → /twenty-partner-match ``` `/twenty-partner-match` chains back into the earlier skills if `partner-match-criteria.md` is missing, and applies critical review if the brief is thin before querying the API. ## Credentials The skill reads `~/.twenty/credentials.env` for API keys (never committed). See `SKILL.md` for setup instructions. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21601?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. --> |
||
|
|
8a866dba54 |
Add call recording schema and meeting bot scaffold (#21584)
## Summary - add 2.13 upgrade commands for call recording request status and dropping CalendarEvent recordingPreference - remove the recording preference from the core CalendarEvent standard object - add a scaffold-generated twenty-meeting-bot app with logo and the CalendarEvent meetingBotPreference field ## Tests - yarn install - yarn lint - yarn twenty dev:typecheck - git diff --check <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21584?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. --> |
||
|
|
d1ba63d4a4 |
Postcard app e2e front component rendering test (#21600)
# Introduction Creating a playwright test, quite granular and verbose that will verify that the post card preview front component is rendered as expected on the tested twenty instance This covers everything e2e from twenty front, front comp renderer, assets cdn rendered redirection etc Style bridge etc ## Note The playwright test setup assumes the application has already been installed once, it's mainly used by the merge queue as a high level front component and logic function ( will be in the same ci ) regression bottleneck The goal isn't for this test to be run locally |
||
|
|
a3fe9efb69 |
chore(apps): bump twenty-sdk & twenty-client-sdk to 2.13.0, vitest to 4 (#21553)
## What Bumps **all 14 `twenty-apps`** (internal, examples, community) to the freshly published SDK **2.13.0**, and upgrades `vitest` 3 → 4 so `vite` resolves to 8 (rolldown). - `twenty-sdk` / `twenty-client-sdk`: `2.10.1` → `2.13.0` (each app's original spec format preserved — plain, `^`, and `npm:…@`). - `vitest`: `^3.x` → `^4.0.0` in the 13 apps that use it (`call-recording` has no vitest). ## Why Each app's `yarn.lock` had open Dependabot esbuild alerts — high `GHSA-gv7w-rqvm-qjhr` and low `GHSA-g7r4-m6w7-qqqr`, both fixed in esbuild `0.28.1`. The SDK bump alone does **not** clear them: the advisories fire on *any* esbuild `< 0.28.1`, and each app pulled a vulnerable `esbuild@0.27.7` transitively via **vite** (through the `vitest` devDependency), independent of the SDK. Bumping `vitest` to 4 resolves `vite@8` (rolldown), which drops the esbuild dependency entirely. `twenty-partners` additionally needed a recursive esbuild re-resolution (its `tsx` dep had `esbuild@~0.28.0` pinned at the still-vulnerable `0.28.0`). After this change, **all 14 lockfiles resolve esbuild `0.28.1` only** — zero copies `< 0.28.1`. ## Test - All 14 lockfiles verified free of esbuild `< 0.28.1`. - vitest 4 + vite 8 confirmed working: `people-data-labs` runs **334 tests across 83 files, all passing**. - `twenty-for-twenty`'s suite fails only because its global setup requires a live Twenty server (`/healthz`) — environmental, would fail identically under vitest 3. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21553?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. --> |
||
|
|
1934fcc261 |
Add last contact twenty app (#21464)
Adds a last contact at column in people object - backfill at installation - update last contact when receiving an email or a calendar event - cron to update last contact with recently passed calendar event @Bonapara can you check the app logo? <img width="909" height="464" alt="image" src="https://github.com/user-attachments/assets/ca1c01a5-9838-4cf0-b0b8-d66a7f88b5fc" /> --------- Co-authored-by: Thomas des Francs <tdesfrancs@gmail.com> Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com> |
||
|
|
e3cfbbffb5 |
Bill People Data Labs enrichments in Twenty credits (#21481)
Adds per-enrichment billing to the People Data Labs app. Each **matched** record charges the workspace in Twenty credits via `chargeCredits` (`twenty-sdk/billing`), following the same pattern as the exa app. - Person match: **336,000 micro-credits** ($0.336 — PDL list price $0.28 + 20% margin) - Company match: **120,000 micro-credits** ($0.12 — PDL list price $0.10 + 20% margin) > **Note:** the 20% margin is a first draft, not final — it's a single constant (`src/constants/billing-margin-multiplier.ts`) and easy to adjust once we settle on pricing. PDL only consumes a credit on a successful match, so `not_found`, errors, and skipped records are free. The charge is emitted once per PDL batch call (≤100 records) with `operationType: CODE_EXECUTION`, `quantity` = match count, and `resourceContext` `pdl/person` / `pdl/company`, at the moment PDL responds — a match whose record write later fails is still billed since the PDL cost was already incurred. Billing failures are non-fatal and never break an enrichment. Prices and margin live as constants in `src/constants/` for easy retuning. No SDK bump needed (`twenty-sdk@2.10.1` already ships `./billing`). |
||
|
|
a0e3c43234 |
People data labs app: remove navigation menu items (#21478)
Removes the People Data Labs app navigation menu — the "People Data Labs" folder and its two view entries (Enriched People, Enriched Companies) — from the sidebar, along with the now-unused navigation menu item identifiers. The "Enriched (PDL)" view definitions are kept and remain available on the Person and Company objects; they just no longer appear as a folder in the navigation menu. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21478?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. --> |
||
|
|
2538239e05 |
People data labs: update app logo (#21479)
Replaces the People Data Labs internal app icon with the new logo. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21479?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. --> |
||
|
|
adbd78767e |
feat(partners): lock admin-managed + ownership fields on Partner role (#21471)
## What Tightens the **Partner** self-service role's field-level permissions so a partner can edit its own profile but not admin/ops-controlled or ownership fields. All locks are `canUpdateFieldValue: false` on the Partner object. **Admin-managed scalar fields (7):** `slug`, `validationStage`, `reviewed`, `ranking`, `partnerTier`, `applicationNotes`, `lastMatchAt` **Ownership relation FKs (2):** `partnerUser`, `company` ## Why - The 7 scalar fields are admin/ops-controlled (validation, ranking, tiering, internal notes) — a partner must not be able to self-promote or alter ops data. - `partnerUser` is the **RLS pivot**: the row-level predicate scopes a partner to records where `partnerUser IS <their workspace member>`. If a partner could clear or repoint it, they'd drop their own record out of scope (an orphan only admins can see). It is already locked on Opportunity; this brings Partner in line. - `company` is read-only at the object level for partners, so its FK link must not be repointable from the Partner side either. The remaining Partner relations (`opportunities`, `persons`, `partnerContents`) need no lock — they are already protected by inverse-side field locks or object-level read-only / no-access rules. ## Scope - One source file: `src/roles/partner.role.ts` (9 field-permission entries). - No schema changes — additive permission tightening; upgrades cleanly via `deploy` + `install`. - Version: patch bump `0.5.0 → 0.5.1`. |
||
|
|
d0884bd708 |
Fix missing datetime filter type (#21451)
Currently datetime fields are only typed to be filtered by string Add a proper typing to match gql filters ## Before <img width="750" height="492" alt="image" src="https://github.com/user-attachments/assets/ff3a5423-3bb0-4295-84c9-e404489354f6" /> ## After <img width="537" height="511" alt="image" src="https://github.com/user-attachments/assets/d8c8219f-b7de-41b0-96cb-5adbfda7a91d" /> |
||
|
|
f7463886a6 |
feat(partners): partner role row-level security (RLS) with scoped edits (#21386)
## Summary
Adds an external **Partner** self-service role that sees and edits only
its own
records via row-level security (RLS), so a validated partner can sign in
and manage
just the deals they're matched on.
## What's included
- **`partnerUser` relation** on Partner, Person, Company, Opportunity (+
inverse
relations on Workspace Member) — the login member a record belongs to.
- **RLS predicates** scoping each of those objects to "partnerUser IS
the current
workspace member", plus a self-scope on Workspace Member so member-typed
relations
resolve without exposing the internal team roster. Applied out-of-band
via
`yarn rls:configure` (the app manifest cannot ship RLS predicates).
- **Assign / unassign cascade** (`on-opportunity-partner-assigned` logic
function):
assigning a Partner to an Opportunity stamps `partnerUser` onto the
Opportunity +
its Company + People; removing the Partner clears it (and cascades to
the
Company/People when no other deal of that member still uses them).
- **Partner role permissions**
- Partner profile: full read/update.
- Opportunity: read all; **update `stage` and `amount` only** (every
other
user-facing field locked).
- Company / Person: read-only.
- Workspace Member: read-only, RLS-scoped to self.
- **`partnerUser` column** added to the Validated Partners view so the
login member
can be assigned inline.
## Install / upgrade note
After install or reinstall, run `yarn rls:configure` (`:prod` variant
for prod) to
(re)apply the RLS predicates and verify the field-permission locks.
Manifest sync
handles object/field permissions; predicates are applied by this script.
## Platform gaps found (for the eng team)
1. **Manifest sync doesn't invalidate the roles-permissions Redis
cache.** Permission
changes deployed via `yarn twenty dev --once` persist to the DB but
aren't reflected
in the cached snapshot used for enforcement until
`engine:workspace:metadata:permissions:roles-permissions:<workspaceId>:{data,hash}`
is flushed. Relevant on any real workspace when permissions change.
2. **Locking a server-injected field silently breaks all updates.** The
`*.updateOne`
pre-query hook writes `updatedBy` into every update, so
`canUpdateFieldValue:false`
on `updatedBy` makes the permission check reject *every* record update
with
`PERMISSION_DENIED`. Field-permission lock lists must exclude
server-managed/injected
fields (`updatedBy`; and `position`, co-written with `stage` on kanban
drag).
## Version
Minor bump → `0.5.0` (new role, new fields, new behaviour;
backwards-compatible).
## Testing
- Verified locally as a partner user: edits own profile; edits
Opportunity stage +
amount; Company/Person read-only; sees only matched deals; unassigning a
partner
removes the deal (and its company/people) from the partner's view.
- `yarn rls:configure` passes (5 predicates upserted; 24 Opportunity
fields locked,
stage + amount editable).
- Lint clean.
|
||
|
|
6756ea628d |
Add People Data Labs enrichment logic functions (#21254)
## Add People Data Labs enrichment logic functions Implements the **enrichment logic functions** that call the PDL API and map responses onto standard + `pdl*` fields. - **Logic functions** — shared core powers bulk workflow actions (`enrich-person` / `enrich-company`) and single-record AI tools. Each guards a TTL, matches via identifier, fills standard fields only when empty, always writes `pdl*` fields, and normalizes SELECT values against field options. - **Data model** — PDL employer now links via the **standard `company` relation** (find-or-create, fill-only-if-empty); removed the dedicated `pdlCurrentCompany` relation, six `pdlJobCompany*` scalars, and the `pdl*` indexes. **Not functional yet:** the post-install workflow seeding (`seed-enrichment-workflow.ts`) is left in place but blocked — it needs workflow-builder mutations that the CRUD-only app SDK schema doesn't expose. **Deferred:** metering/billing and auto-enrichment triggers. https://github.com/user-attachments/assets/a570a484-ae79-4c57-a274-e068fdab78e4 https://github.com/user-attachments/assets/6edfb5e1-cada-4892-87e0-b485eb359692 |
||
|
|
6e147a548b |
security: clear twenty-apps & seed-dependencies CVE alerts (#21410)
Clears the Oneleet/dependency CVE alerts from the `twenty-apps` example/internal app lockfiles and the application-package `seed-dependencies` template — all via parent/direct dependency upgrades, **no `resolutions` overrides**. ## Lock refresh (non-breaking, within existing ranges) - **postcss** 8.5.8/8.5.9 → 8.5.15 — CVE-2026-41305 — postcard, hello-world, self-hosting - **ip-address** 10.1.0 → 10.2.0 — CVE-2026-42338 — postcard, hello-world, self-hosting, twenty-for-twenty - **yaml** 1.10.2 → 1.10.3 — CVE-2026-33532 — call-recording ## seed-dependencies (direct/parent bumps) - **uuid** `^10.0.0 → ^11.1.1` (direct) — CVE-2026-41907 - **body-parser** `^1.20.4 → ^1.20.5`, which pulls **qs** 6.15.2 — CVE-2026-8723 - **socks** 2.8.3 → 2.8.9 (refresh), which pulls **ip-address** 10.2.0 — CVE-2026-42338 ## twenty-for-twenty - **resend** 6.12.0 → 6.12.4 (refresh): 6.12.4 drops the `svix` dep that pulled the vulnerable **uuid** 10.0.0, leaving only uuid 13.0.2 — CVE-2026-41907 All flagged packages were transitive (except the direct seed-deps `uuid`); no app source changes. |
||
|
|
a825dcf2cc |
security: clear 8 Dependabot alerts via transitive/parent bumps (no resolutions) (#21409)
Clears 8 Dependabot alerts via in-range transitive/parent bumps and one dead-dependency removal. **No `resolutions` overrides** were used — every fix is a real version bump within existing semver ranges or a parent upgrade. ### Root `yarn.lock` - **react-router** 6.30.3 → 6.30.4 (open redirect via protocol-relative URL) — pulled through react-router-dom, ranges unchanged — alert #1382 - **yaml** 2.8.1 → 2.9.0 (stack overflow on deeply nested collections) — alert #734 - **uuid** `^13.0.0` → 13.0.2 in twenty-sdk + create-twenty-app (buffer bounds check) — alert #1164 - **ip-address** `^9.0.5` dropped by bumping **socks** 2.8.3 → 2.8.9 (now depends on `ip-address ^10.1.1`, which is unaffected) — alert #1171 ### `seed-dependencies` lockfile - **uuid** `^10.0.0` → `^11.1.1` (direct dep; removed now-redundant `@types/uuid` since uuid v11 ships its own types) — alert #1287 - **ip-address** `^9.0.5` dropped via the same socks bump — alert #1170 ### `twenty-for-twenty` lockfile - **resend** bumped to 6.12.4 (`^6.12.0` range kept), which drops its `svix@1.90.0 → uuid@^10` transitive chain — alert #1278 ### `twenty-companion` - Removed the unused **simplemde** dependency. The note editor loads SimpleMDE from a CDN `<script>` tag and never imports the npm package; `easymde` (its maintained fork) is already a dependency — alert #690 ### Not addressed here The remaining alerts can't be closed without `resolutions` overrides (deliberately avoided in this PR) or a larger migration: - **qs** (#1305, #1304), **lodash** (#824 high / #823 / #385), **ws** (#1238), **postcss** (#1061) — vulnerable copies are pinned exact / bundled by parents (express, body-parser, @nestjs/*, next, styled-components, zapier) with no in-range patch. - **webpack-dev-server** (#1237/#692/#691) — pinned by `@electron-forge/plugin-webpack` (still on v4); dev-tooling only. - **uuid <11.1.1** (#1289) — spread across `^3`/`^8`/`^9` transitive ranges; reaching v11 is a breaking jump. - **apollo-server-core** (#735/#736) — requires an Apollo Server 3 → 4 migration. |
||
|
|
6971d6fa95 |
Prevent self-hosting app from re-matching/re-creating people on no-op updates (#21406)
The match-telemetry-event-with-people logic function triggers on selfHostingUser.* (both created and updated) and unconditionally wrote personId back to the record on every run. Since the handler's own write produces an updated event — and the telemetry webhook also updates unrelated fields (name, serverUrl, etc.) on returning signups — the matching logic re-ran unnecessarily, querying people and writing on each pass. This adds an early-return guard so the handler only does work when there's actually something to match: Skip when the selfHostingUser is already linked (personId set) and the primary email hasn't changed. Still (re)match on first link and on genuine email changes — the legitimate reasons for listening on updated. The guard reads before.email.primaryEmail via an 'before' in properties narrowing so it stays type-safe across the create/update event union. |
||
|
|
99bd1daaec |
feat(twenty-partners): website field + restructured partner & opportunity views (#21385)
## What Twenty Partners app — partner enrichment + a simpler, partner-centric view structure. **Partner** - New `website` (LINKS) field. - Icons for the opportunity `partner` relation and `matchStatus` fields (were the default "123"). **Partner views** (Partners folder → Applications / Validated / All) - **Applications**: grouped by validation stage, showing Application + Potential; columns Categories / Skills / Type of Team / Languages / Country / LinkedIn. - **Validated**: grouped by availability; columns harmonized with Applications, plus Partner Tier. - Wider Name / Categories columns. **Opportunity pipeline** (Pipeline folder, simplified by partner presence) - `OPP without partner` / `OPP with partner` tables (filtered on partner *is empty* / *is not empty*), `OPP all`, and a kanban **board** grouped by match status. - Sort by match status (uses the option position, i.e. pipeline order). - Removes the previous matching views (waiting-for-match, matches-overview, partner-deals). ## Version Patch bump `0.4.2 → 0.4.3` (relative to main). |
||
|
|
ca4fc5615f |
security: refresh lodash + picomatch in twenty-apps lockfiles (#21378)
## What Clears the 4 remaining High alerts in the standalone `twenty-apps` lockfiles (hello-world, call-recording). Both are transitive and already in-range, so a plain lockfile refresh picks up the patched releases — no resolutions. | Package | From → To | Requested by | Advisory | |---|---|---|---| | lodash | 4.17.x → 4.18.1 | `@genql/runtime` (`^4.17.20`), `twenty-client-sdk` (`^4.17.21`) | GHSA-r5fr-rjxr-66jc | | picomatch | 4.0.x → 4.0.4 | `tinyglobby` (`^4.0.3`) | GHSA-c2c7-rcm5-vvqj | Only the two app `yarn.lock` files change. These are isolated example/internal apps (not in the root workspace), in the same family as the already-merged #21371 / #21374. |
||
|
|
e51efef7c8 |
security(apps): bump twenty-sdk to 2.10.1 for the 3 remaining pre-2.0 apps (tmp, undici) (#21374)
## Summary Completes the follow-up flagged in #21344, which deliberately deferred the **three apps pinning a pre-2.0 `twenty-sdk`** (a major jump that needed per-app validation). These were the last `twenty-apps/*` lockfiles still carrying the `tmp` + `undici` Dependabot clusters: | App | SDK before | SDK after | |---|---|---| | `examples/hello-world` | `0.9.0` | `2.10.1` | | `internal/call-recording` | `0.6.3-alpha` | `2.10.1` | | `internal/self-hosting` | `1.22.0-canary.6` | `2.10.1` | Bumping to `twenty-sdk@2.10.1` drops the two vulnerable transitive deps these apps still inherited (via `inquirer ^10 → external-editor`, and `@genql/cli`): | Vuln dep | Advisory | Source | |---|---|---| | `tmp@0.0.33` | [GHSA-ph9p-34f9-6g65](https://github.com/advisories/GHSA-ph9p-34f9-6g65) / CVE-2026-44705 (path traversal) | `inquirer ^10 → external-editor` | | `undici@5.29.0` | [GHSA-vrm6-8vpv-qv8q](https://github.com/advisories/GHSA-vrm6-8vpv-qv8q) / CVE-2026-1526 (websocket OOM) | `@genql/cli` | ## Changes - Bump `twenty-sdk` (and `twenty-client-sdk` where pinned) to `2.10.1` in all 3 apps + regenerate each lockfile. - `hello-world` and `self-hosting` migrate transparently (typecheck clean). - `internal/call-recording` needed source changes for the 2.x API: - `twenty-sdk/clients` → `twenty-client-sdk/core` + `twenty-client-sdk/metadata` (5 files); added `twenty-client-sdk` dependency. - `defineRole` `permissionFlags` → `permissionFlagUniversalIdentifiers` (`SystemPermissionFlag`) — real runtime fix (old key is silently ignored in 2.x). ## Verification Per-app after regen: **`tmp@0.0.33` = 0**, **`undici@5` = 0** in every lockfile; `oxlint` passes with **0 errors**. Root `yarn.lock` untouched; all other undici in the repo is already ≥ patched (`6.26.0` / `7.24.8`). |
||
|
|
123db9e3be |
security: bump vite to 7.3.5 in twenty-apps lockfiles (GHSA-v2wj-q39q-566r) (#21371)
## What The standalone apps under `packages/twenty-apps/*` each ship **their own `yarn.lock`** (they're not part of the root workspace). Three of them still pinned the vulnerable transitive `vite@7.3.1`: - `examples/hello-world` - `examples/postcard` - `internal/call-recording` `vite <= 7.3.1` is affected by three advisories, all first patched in **7.3.2**: | Advisory | Summary | Open Dependabot alerts | |----------|---------|------------------------| | [GHSA-v2wj-q39q-566r](https://github.com/advisories/GHSA-v2wj-q39q-566r) (CVE-2026-39364) | `server.fs.deny` bypassed with queries | #894, #892, #891 | | [GHSA-4w7w-66w2-5vf9](https://github.com/advisories/GHSA-4w7w-66w2-5vf9) | Path traversal in optimized-deps `.map` handling | #901, #899, #898 | | [GHSA-p9ff-h696-f583](https://github.com/advisories/GHSA-p9ff-h696-f583) | Arbitrary file read via dev-server WebSocket | #908, #906, #905 | The root `yarn.lock` was already remediated separately (vite 7.3.2 / 8.0.16); these three sub-package lockfiles were the only ones still flagged open. ## How Ran `yarn up -R vite` per app to re-resolve vite within the existing range; it lands on **7.3.5**. ## Scope - **Lockfile-only**, 3 apps. No `package.json` changes. - Each lockfile diff is 3 lines (version / resolution / checksum). - Verified no vite resolution below the patched thresholds remains anywhere in the repo. |
||
|
|
9c66975520 |
isCustom deprecation for Objects and Fields (#21228)
## Context
`isCustom` was a legacy denormalized boolean on `ObjectMetadataEntity`
and `FieldMetadataEntity`.
Now that every metadata row carries `applicationId` (via
`SyncableEntity`), "is this custom" is fully derivable, and the stored
boolean was a redundant second source of truth that could drift.
The real meaning of `isCustom` is **"the owning application is not the
twenty-standard application"** — i.e. `!belongsToTwentyStandardApp`.
Note this is *not* "belongs to the workspace custom app" as I initially
thought: third-party-application
objects/fields are custom too.
The standard application has a globally stable `universalIdentifier`, so
the value derives with no per-workspace lookup.
## Changed
## `isCustom` checks — before → after
`isCustom` is no longer a stored column. The table below lists every
site that branched on it and how it resolves now. The unifying rule:
`isCustom ≡
!isTwentyStandardApplicationUniversalIdentifier(applicationUniversalIdentifier)`.
### Server — behavioural checks
| Location | Purpose | Before | Now |
|---|---|---|---|
| `utils/compute-object-target-table.util.ts` | Physical table name `_`
prefix | `computeTableName(nameSingular, objectMetadata.isCustom)` |
derives from `applicationUniversalIdentifier` (single source for all
table-name callers) |
| `twenty-orm/factories/entity-schema.factory.ts` +
`…/entity-schema-metadata.type.ts` | ORM table name (hot path) |
`object.isCustom` | `object.applicationId !== standardApplicationId`
(computed in `buildEntitySchemaMetadataMaps`) |
|
`twenty-orm/repository/workspace-{delete,soft-delete,update}-query-builder.ts`
| Table name for mutations | `computeTableName(nameSingular,
objectMetadata.isCustom)` | `computeObjectTargetTable(objectMetadata)` |
| `index-metadata/utils/generate-deterministic-index-name-v2.ts` | Index
name hash (must stay bit-identical) | `flatObjectMetadata.isCustom` |
derives from `applicationUniversalIdentifier` |
| `object-metadata/object-record-count.service.ts` | Table name for
record count | `computeTableName(nameSingular, isCustom)` |
`computeObjectTargetTable(flatObjectMetadata)` |
|
`workspace-manager/dev-seeder/data/services/dev-seeder-data.service.ts`
| Match seed config by table name | `computeTableName(item.nameSingular,
item.isCustom)` | `computeObjectTargetTable(item)` |
| `commands/workspace-export/workspace-export.service.ts` +
`…/utils/generate-workspace-schema-ddl.util.ts` | Export table name (raw
entity) | `objectMetadata.isCustom` |
`!isTwentyStandard…(objectMetadata.application?.universalIdentifier)` |
|
`flat-field-metadata/services/flat-field-metadata-type-validator.service.ts`
| Block users creating reserved field types |
`args.flatEntityToValidate.isCustom` |
`!args.flatEntityToValidate.isSystem` |
| `api/common/.../common-create-many-query-runner.service.ts` | Don't
let client overwrite system `createdBy` |
`createdByFieldMetadata.isCustom === false` |
`createdByFieldMetadata.isSystem === true` |
|
`field-metadata/utils/resolve-field-metadata-standard-override.util.ts`
| Skip i18n/overrides for custom fields | `if (fieldMetadata.isCustom)
return raw` | **removed** — falls through on
`isDefined(standardOverrides)` |
|
`object-metadata/utils/resolve-object-metadata-standard-override.util.ts`
| Skip i18n/overrides for custom objects | `if (objectMetadata.isCustom)
return raw` | **removed** — same fall-through |
|
`command-menu-item/utils/build-navigation-interpolation-context.util.ts`
| Override context for nav labels | passed `isCustom` into resolver |
dropped (resolver no longer needs it) |
| `api/common/.../data-arg-processor.service.ts` | `isCustom` for
record-position table name | `flatObjectMetadata.isCustom` | derives
from `applicationUniversalIdentifier` |
| `metadata-modules/minimal-metadata/minimal-metadata.service.ts` |
Minimal DTO + override context | `flatObjectMetadata.isCustom` | derives
from `applicationUniversalIdentifier` |
|
`commands/upgrade-version-command/1-23/…backfill-record-page-layouts.command.ts`
| Filter to custom objects | `objectMetadata.isCustom` |
`!isTwentyStandard…(applicationUniversalIdentifier)` |
### Server — DTO / API population
| Location | Before | Now |
|---|---|---|
|
`flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util.ts`
| passthrough `isCustom` | derives from `applicationUniversalIdentifier`
|
|
`flat-field-metadata/utils/from-flat-field-metadata-to-field-metadata-dto.util.ts`
| passthrough `isCustom` | derives from `applicationUniversalIdentifier`
|
|
`object-metadata/utils/from-object-metadata-entity-to-object-metadata-dto.util.ts`
(REST) | `entity.isCustom` | `entity.applicationId !==
standardApplicationId` |
|
`field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util.ts`
(REST) | `entity.isCustom` | `entity.applicationId !==
standardApplicationId` |
| `dataloaders/dataloader.service.ts` | passed
`flatFieldMetadata.isCustom` into override resolver | dropped (resolver
no longer needs it) |
> REST controllers (`object-metadata.controller.ts`,
`field-metadata.controller.ts`) resolve `standardApplicationId` once per
request from the cached `flatApplicationMaps`.
### Frontend
| Location | Purpose | Before | Now |
|---|---|---|---|
| `settings/.../SettingsObjectFieldDisabledActionDropdown.tsx` | Whether
an inactive field is deletable | `isDeletable = isCustomField` |
`isDeletable = isCustomField && !isSystemField` |
### Unchanged (out of scope)
`isCustom` on `IndexMetadata` / `View` / `Skill` / `Agent` and their
guards still read the persisted column.
Breaking change is on the isCustom filter on field and object APIs, this
is never used in the FE and unlikely used by external consumers
|
||
|
|
a48c158a66 |
security(apps): bump twenty-sdk to 2.10.1 across twenty-apps (tmp, undici) (#21344)
## Summary Propagates the just-published **`twenty-sdk@2.10.1`** security patch into the `twenty-apps/*` mini-apps, clearing the bulk of the nested-lockfile Dependabot alerts (the `tmp` + `undici` clusters). Each app carries its own `yarn.lock`, so the fix only reaches them once they bump the SDK. `2.10.1` drops the two vulnerable transitive deps every app inherited: | Vuln dep | Source | Fixed by | |---|---|---| | `tmp@0.0.33` (GHSA-ph9p / GHSA-52f5) | `inquirer ^10 → external-editor` | `inquirer ^14` → `@inquirer/editor@5` (no external-editor) | | `undici@<6.24` (5 GHSAs) | `@genql/cli` | vendored genql codegen (`@genql/cli` removed) | ## Changes Bumps `twenty-sdk` **and** `twenty-client-sdk` (whichever each app pins — several pin both) to `2.10.1` and regenerates each lockfile. **10 apps updated** (all on the v2 line — minor bump, low risk): `twenty-slack`, `twenty-discord`, `twenty-linear`, `twenty-partners`, `twenty-fireflies`, `people-data-labs`, `twenty-for-twenty`, `exa`, `github-connector`, `postcard`. Verified per-app after regen: **`tmp@0.0.33` = 0** and **`undici@5` = 0** in every updated lockfile. ## Deliberately excluded Three apps pin a **pre-2.0** SDK, where `→ 2.10.1` is a major jump that risks breaking the app and needs per-app validation: - `examples/hello-world` (`0.9.0`) - `internal/call-recording` (`0.6.3-alpha`) - `internal/self-hosting` (`1.22.0-canary.6`) These still carry one `tmp`/`undici` alert each and should be handled in a follow-up. ## Related - `twenty-sdk@2.10.1` release (tag `sdk/v2.10.1`) — backport of #21339 (undici) + #21340 (tmp) from `main`. |
||
|
|
bf75ab8982 |
feat(twenty-partners): notify Discord on new partner application (#21313)
Adds an `on-partner-application-created` logic function triggered on the `partner.created` database event. When the website application form creates a new Partner, it posts a rich embed to a Discord channel (applicant, company, country, languages, partner scope, skills) with a deep link to the record. ## How it works - Fires only on genuine form submissions — discriminates via `createdBy.source === 'APPLICATION'`, which excludes seed/import (`API`) and manual UI (`MANUAL`) creation. - Runs out-of-band on the worker (database event trigger), so it adds **no latency** to the applicant's submission, and the linked Person already exists by the time it runs. - Best-effort: a Discord failure never fails the trigger (wrapped in `try/catch`, 8s timeout). ## Configuration (per workspace — Settings → Apps → Twenty Partners → Variables) - `DISCORD_WEBHOOK_URL` (secret) — the incoming webhook URL. **The feature is a no-op when unset.** - `PARTNER_APP_FRONTEND_URL` — workspace front-end base URL for the record deep link (e.g. `https://partners.twenty.com`). ## Notes - New logic function + two application variables; version bumped to **0.4.0** (minor). - Unit tests cover the source-guard branches, the on/off switch, the embed contents/ordering, and best-effort failure handling. - The website and the existing `submit-partner-application` handler are untouched. |
||
|
|
5a2523f533 |
chore(apps): bump vitest to 3.2.6 in twenty-apps projects (GHSA-5xrq-8626-4rwp) (#21336)
Resolves the **vitest Critical** Dependabot alerts
(`GHSA-5xrq-8626-4rwp`, vitest `< 3.2.6`) — #1422–#1433.
Each `packages/twenty-apps/*` project is an **independent yarn project**
with its own `package.json` + `yarn.lock` (not part of the root
workspace). 12 of them declared `vitest: ^3.1.1` and locked an older
3.2.x. This bumps the range to `^3.2.6` and refreshes each lockfile to
**3.2.6** (latest 3.x, published 2026-06-01).
Projects updated: `community/github-connector`,
`examples/{hello-world,postcard}`,
`internal/{exa,people-data-labs,self-hosting,twenty-discord,twenty-fireflies,twenty-for-twenty,twenty-linear,twenty-partners,twenty-slack}`.
- Dev-scope only (test runner); no runtime impact.
- The **root workspace already uses vitest 4.x** (≥ the fix) and is
intentionally untouched.
- Verified: no `vitest < 3.2.6` remains in any `twenty-apps` lockfile.
|
||
|
|
0403762516 |
security: refresh twenty-apps lockfiles for vulnerable transitive deps (#21316)
## What The standalone apps under `packages/twenty-apps/*` each ship **their own `yarn.lock`** (they're not part of the root workspace), and those lockfiles still pulled vulnerable transitive versions of `axios`, `undici`, `tmp`, `qs`, `ws`, `brace-expansion`, `uuid` (via `twenty-sdk` / `twenty-client-sdk`). This was ~130 of the open Dependabot alerts — none of them reachable from the root-lockfile PRs. Ran `yarn up -R` per app to re-resolve the vulnerable transitives within their existing ranges, across all 13 flagged apps: - **`axios` → 1.17.0** — clears the entire proxy-auth-leak / ReDoS / config-merge MITM advisory set (the 56 axios alerts) - **`qs`, `brace-expansion`, `uuid`** → patched - **`undici`, `ws`** → patched on the in-range majors (older majors that parents pin exactly remain, same situation as the root lockfile) ## Scope - **Lockfile-only**, 13 apps. No `package.json` changes. - Test **fixtures** (`packages/twenty-apps/fixtures/*`) intentionally left untouched — Dependabot didn't flag them and they back snapshot tests. |
||
|
|
128d2d394d |
feat: allow apps to add view fields to existing views (defineViewField) (#21160)
## Summary
Lets a Twenty application add **view fields (columns) to an existing
view it does not own** — including standard views like the People index
view — without redeclaring/owning that view. This mirrors the existing,
working pattern by which an app adds a custom field to a standard object
via `defineField` + `objectUniversalIdentifier`.
The asymmetry being removed was purely in the manifest schema:
`ViewFieldManifest` only existed *nested* inside
`ViewManifest.fields[]`, so adding a view field forced declaring a
`ViewManifest` — which the sync treats as a view the app creates and
owns, and rejects when the UID is a standard view's. Validation,
persistence, the FK aggregator machinery, and uninstall cleanup were
already generic and cross-app-safe, so no engine changes were needed.
### Changes
- **twenty-shared:** new top-level `StandaloneViewFieldManifest`
(`ViewFieldManifest & { viewUniversalIdentifier }`),
`Manifest.viewFields`, and a `SyncableEntity.ViewField` member.
- **twenty-sdk:** `defineViewField` (validates `universalIdentifier` +
`viewUniversalIdentifier` + `fieldMetadataUniversalIdentifier`), CLI
manifest assembly of a top-level `viewFields` list, and `dev:add
viewField` scaffolding.
- **twenty-server:** one top-level loop over `manifest.viewFields` that
reuses the existing `fromViewFieldManifestToUniversalFlatViewField`
converter (already parameterized by `viewUniversalIdentifier`). No
validator/persistence/aggregator changes.
### Notes for maintainers
- Confirm the `Manifest.viewFields` optionality convention — implemented
as a **required** array to mirror `fields`/`views`.
- Two different apps adding a column for the same field to the same view
conflicts on the existing unique `(fieldMetadataId, viewId)` partial
index; the existing `flat-view-field-validator` duplicate check surfaces
this as a structured validation error.
- `dev:add viewField` scaffolding is included (was optional in the
plan).
## Test Plan
- [x] `twenty-shared` typecheck
- [x] `twenty-sdk` 364 unit tests + `buildManifest` assembly test
(rich-app fixture) + typecheck + prettier
- [x] `twenty-server` typecheck + `lint:diff-with-main`
- [x] **Server integration suite**
`successful-manifest-update-view-field.integration-spec.ts` (4/4):
- standalone view field attaches to the standard `allPeople` view
without recreating it (sync succeeds, no
`INVALID_VIEW_DATA`/`ENTITY_ALREADY_EXISTS`)
- uninstall removes the contributed column while the standard view + its
columns remain intact
- duplicate `(view, field)` rejected with `METADATA_VALIDATION_FAILED`
- unknown target view rejected
- [x] Sibling `successful-manifest-update-field.integration-spec.ts`
still green (no harness regression)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||
|
|
36b654bab3 |
Scaffold people data labs enrichment app (#21175)
# 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. |
||
|
|
15eaabdbc1 |
fix(ai) - optimize crud tools (#21133)
- **Add delete many**, `delete_many_{object}` added alongside the
existing `delete_one_{object}`.
- **Uniformize naming**, crud module, type names, and MCP helper
constants renamed for consistency.
- **Optimize tool schema (learn phase)**
- `find_many(_companies)`: **7 158 → 2 700 tokens**
- `find_one(_company)`: **280 → 126 tokens**
- ....
- Main mechanism: `reused: 'ref'` (line 7 of
`to-tool-json-schema.util.ts`). Zod walks the schema tree, tracks which
Zod schema instances appear more than once, and emits each reused
instance exactly once in `$defs`, replacing all subsequent occurrences
with a `$ref`. Works because filter and value schemas are now extracted
as shared objects.
- **Optimize system prompt (tool catalog)**, DATABASE_CRUD section
restructured to list operation patterns (`find_many_{object}`, …) once +
objects once, instead of the full N×M cross-product of tool names.
- **Optimize execute_tool**, shared record-properties schema (same
`$defs` deduplication applies at call time); introduced `upsert_many`;
added `selectedFields` to `find_*` so the agent only fetches the fields
it needs.
|
||
|
|
c2ad3f3614 |
(partners): bump app version 0.3.3 -> 0.3.4 (#21167)
Forgot to bump the version when merging #21162 from `0.3.3` to `0.3.4` |
||
|
|
ff5d082e7c |
feat(partners): remove Project Budget Typical field, rework partner views & nav order (#21162)
## Summary Partners-app changes spanning the Partner object, its data scripts, table views, and sidebar navigation. ### Remove the "Project Budget Typical" field Dropped the `projectBudgetTypical` currency field from the Partner object and every reference to it: - `get-partner-by-slug` and `list-available-partners` logic-function selections - the seed script (type, write mapping, and per-partner data) - the `import-from-tft` mapping (also dropping the now-unused `partnerBudgetAverage` TFT source selection) `projectBudgetMin` is intentionally kept. ### Rework partner views - **Partners** (all-partners) view: replaced the **Deployment Expertise** column with **Categories** (the `partnerScope` field). - **Validated partners** view: added a **Languages Spoken** column. - Set view `position`s so the in-object view switcher orders **Validated → Applications → Partners**. ### Navigation order Reordered the "Partners" folder navigation items so the sidebar reads **Validated partners → Partner applications → Partners** (Partner content stays last). ### Also included The previously-pushed fix that excludes partners with an empty slug from the available-partners list. ## Notes - No deploy/sync performed. The view-column and navigation-ordering changes take effect once the app manifest is synced (`yarn twenty dev --once` locally). - The `deploymentExpertise` field itself is unchanged — only its column was removed from the all-partners view. |
||
|
|
ea84aabe4c |
chore(twenty-partners): refine design-doc skill doctrine (#21151)
## Summary Iterative refinements to the partner design-doc doctrine after running it on a second lead (TADA) and reviewing output side by side. Touches only the `twenty-partner-design-doc` skill files (doctrine + Claude Code wrapper); no runtime / app code. **What changed** - **Flag system:** emoji + short text label pairs only (`🔮 inf.`, **❓ open**, **⚠️ heavy**, **🛑 blocker**). Replaces the prior text-tag-only system; scannable, unambiguous. - **Section structure:** split into **Required** (always present) and **Conditional** (Views, Automations, Integrations, Reporting). Include conditional sections only when the client grounded them in the source. Number sequentially, no gaps. - **No filler placeholders:** banned `X was not named` / `left out on purpose` lists in body sections. Unknowns belong in Open questions, not as their own section or bullet. - **Functional cross-refs:** every `§N` reference is now a markdown anchor link `[§N](#n-section-slug)`, so a partner skimming the doc can navigate. Bare `§N` is banned. - **Bullets and tables over paragraphs**, with **Open questions** kept as a numbered list (so the partner can read items 1, 2, 3 with the client). - **Views & navigation** rendered as a tight `Surface | Shows | Audience` table. No view-type column — table / kanban / page layout is the partner's call, not a scoping decision. - **Data-model table** gains a `Source` column (`client` / `inf.`) for at-a-glance fact-vs-inference visibility. - **Business decisions over technical mechanics:** cut SDK / runtime internals that don't move the quote (Docker version, OAuth flavour, auto-system relations, env-var names, CI/CD workflow detail). - **Common-mistakes table** updated with rows for the new rules. - **SKILL.md self-check** expanded so the wrapper enforces all of the above before saving. ## Test plan - [ ] Re-read doctrine end-to-end for internal consistency - [ ] Verify the four canonical emoji + text pairs appear and no stray emoji flags remain - [ ] Confirm Required vs Conditional structure is internally consistent (no section listed in both) - [ ] Confirm functional-cross-ref rule appears in both Rules and Formatting and is reflected in the SKILL.md self-check - [ ] Confirm Views & navigation entry mandates the three-column table and bans a Type column - [ ] Confirm Common-mistakes table covers each new rule |
||
|
|
e721ebe300 |
chore(twenty-partners): bump app version to 0.3.3 (#21140)
Bumps the `twenty-partners` SDK app version 0.3.2 → 0.3.3 so `main` tracks what's deployed to prod. This is the deploy version for the partner-app changes that just landed: marketplace `partnerScope` exposure (#21126), the `submit-partner-application` endpoint + new Partner categories + migration (#21040), the marketplace card rebind (#21127), and the signup wizard (#21039). No code changes — version bump only. |
||
|
|
b2539f5b6a |
Prevent conditional availability variables from being used at runtime (#21110)
Fixes https://github.com/twentyhq/twenty/issues/21094 Conditional availability variables (`objectMetadataItem`, `numberOfSelectedRecords`, `objectPermissions`, operators like `everyEquals`/`none`, etc.) are compile-time-only constructs used in `conditionalAvailabilityExpression`. They were previously exported from `twenty-sdk/front-component`, which let developers mistakenly import them into runtime component code where they have no value. - Move conditional availability variables from `twenty-sdk/front-component` to `twenty-sdk/define`. - Add a build-time manifest validation (validate-conditional-availability-usage) that fails the build if these variables are imported/used outside of `conditionalAvailabilityExpression`. - Update the github-connector example app to register commands via dedicated *.command-menu-item.ts files instead of inline command config in front components. - Update docs (all locales) and test mocks to reflect the new import paths. |
||
|
|
4f47885054 |
feat(twenty-partners): submit-partner-application HTTP logic function (#21040)
## Summary Adds a public `POST /partner-applications` HTTP logic function on the twenty-partners SDK app that receives applications from the website wizard and idempotently upserts the Partner / Person / Company graph in the partners workspace. Also introduces the validated **Category** taxonomy on `partnerScope` (additive, prod-safe) plus the legacy→new migration tooling. Companion PR (website side): #21039 ### Logic function - `defineLogicFunction({ httpRouteTriggerSettings: { path: '/partner-applications', httpMethod: 'POST', isAuthRequired: false, forwardedRequestHeaders: ['x-application-secret'] } })`. - Authenticates via shared-secret header (`X-Application-Secret` ↔ `PARTNER_APPLICATION_SECRET` workspace variable). Twenty's `isAuthRequired: true` only accepts user-session JWTs, so the handler enforces auth itself. - Idempotent upsert keyed on `Person.emails.primaryEmail`: - missing email → create Company → Person → Partner - existing Person, no Partner → create Company + Partner, link - existing Person + Partner → update Partner fields; preserve staff-owned columns (`validationStage`, `reviewed`, `ranking`, `partnerTier`, `lastMatchAt`) by omitting them from the update - Create-time defaults preserved on resubmit: `slug = slugify(companyName)` ("YC Agency" → "yc-agency"), `reviewed = false`, `partnerTier = 'NEW'`. - Currency conversion to `{ amountMicros, currencyCode: 'USD' }` for `hourlyRate` + `projectBudgetMin`. ### Categories (`partnerScope`) — additive, prod-safe - Adds 5 validated category options — `ADVISORY`, `SOLUTIONING`, `DEVELOPMENT`, `HOSTING`, `SUPPORT` — to the `partnerScope` MULTI_SELECT **without removing** the legacy options (there is production data on them). Field relabeled **"Categories"**. The website form only emits the new values. - **Migration tooling** (run deliberately, *not* in CI): `scripts/migrate-partner-scope.ts` remaps existing records legacy→new — dry-run by default, `MIGRATE_APPLY=1` to write, two-pass (collect-then-apply, no mutate-while-paginating). `scripts/partner-scope-map.ts` is the single mapping source; `import-from-tft.ts` now routes imported scope through it so the TFT import never re-introduces retired values. Removing the legacy options is deferred until after the migration has run + been verified. ### applicationNotes - New `applicationNotes` TEXT field holds the wizard's single free-text "anything else" note (the handler passes it through directly). `deploymentExpertise` was dropped from the handler input/validation/builders (the column is retained for now, pending the same migration cleanup). ### Application variable - Declares `PARTNER_APPLICATION_SECRET` with `isSecret: true` so each workspace sets the value via Settings → Apps → Twenty Partners → Variables. Twenty encrypts at rest and merges the decrypted value into the handler's `process.env` at execution time (workspace value wins over container env). ### Code quality (from review) - One shared `slugify` (`scripts/slugify.ts`, the import's algorithm) used by both the handler and the import, so the `slug` identity key can't diverge across paths. - Unit-test tier: `vitest.unit.config.ts` (no `globalSetup`) + `yarn test:unit`, so the pure `mapLegacyScope` test runs without a live server (the integration suite stays server-backed). ## Demo 📹 _Screen recording of the wizard end-to-end (open → walk steps → submit → Partner record lands):_ https://github.com/user-attachments/assets/7458dd86-e3ff-47b5-9878-0eb134ff38e3 ### Tests - Integration tests against a local Twenty workspace: missing-/wrong-secret auth rejections, create flow (asserts slug + `reviewed: false` + `partnerTier: 'NEW'`), update-on-resubmit + staff-column preservation, new category values stored, `applicationNotes` stored, bad-input shape. - Pure `mapLegacyScope` unit test via `yarn test:unit` (no server). ## Test plan - [ ] Install / upgrade the app on the target workspace; set `PARTNER_APPLICATION_SECRET` in Settings → Apps → Twenty Partners → Variables - [ ] `curl -i -X POST <workspace-url>/s/partner-applications -H 'X-Application-Secret: <secret>' -H 'Content-Type: application/json' -d '{"firstName":"Test","lastName":"User","email":"test@example.com","companyName":"YC Agency","partnerScope":["ADVISORY"],"applicationNotes":"hi"}'` → `HTTP/1.1 201` + `{"ok":true,"created":true,"partnerId":"..."}` - [ ] Partner record shows `name: "YC Agency"`, `slug: "yc-agency"`, `validationStage: APPLICATION`, `reviewed: false`, `partnerTier: 'NEW'`, `partnerScope: ["ADVISORY"]`, `applicationNotes: "hi"` - [ ] Re-curl same email with `city: "Paris"` → `created: false`, `Partner.city` updated, staff-owned columns untouched - [ ] Wrong / missing secret → `200` + `{"ok":false,"reason":"unauthorized"}` - [ ] `yarn test:unit` green (no server); `yarn migrate:partner-scope` dry-run lists any legacy→new remaps without writing |
||
|
|
a3557373e6 |
feat(twenty-partners): expose partnerScope on list + by-slug endpoints (#21126)
## What Adds `partnerScope` (the partner **Categories** multi-select) to the output of the two public partner endpoints: - `list-available-partners` (`/s/partners`) - `get-partner-by-slug` (`/s/partner-by-slug`) Additive only — `deploymentExpertise` is kept, so existing consumers (the current live marketplace) are unaffected. ## Why Part of the partner marketplace rework. The website marketplace (companion branch `rk-rework-marketplace-cards`) consumes `partnerScope` to show/filter partner Categories. The new options + migration live in the signup app PR #21040. ## Merge order (we'll decide) Independent diff — can merge in any order. Couplings to keep in mind: - **Version line:** this branch and #21040 both bump the app `package.json` version; whoever merges second re-bumps. - **Deploy (not merge):** the partners app is deployed manually. Deploy the final combined app (this + #21040) and run `yarn migrate:partner-scope:prod` **before** the website is deployed. |
||
|
|
53392f9a16 |
feat(twenty-partners): partnerContent catalog + TFT import improvements (#20904)
## Summary Two related threads for the internal `twenty-partners` app: 1. **Redesign `partnerQuote` → `partnerContent`.** The object was mis-modeled as a sales/pre-invoice doc (`amount`, opportunity link). In TFT it's actually a marketing-content catalog — customer quotes, case studies, partner quotes, logos — moving through a production lifecycle. This renames it in place and reshapes it to mirror TFT's `CustomerContent`. 2. **Import tooling improvements** to the TFT importer + multi-env workflow. ## Changes **Schema (`partnerContent`)** - Rename `partnerQuote` → `partnerContent` (object, view, nav, relation fields, identifiers). - Add `contentType` MULTI_SELECT `[CUSTOMER_QUOTE, CASE_STUDY, PARTNER_QUOTE, LOGO]` and `interview` LINKS. - Add `customerCompany` / `customerPerson` relations; keep `partner`; drop the `opportunity` link (TFT has none). - Drop `amount`; rename the FILES field `quoteFile` → `documents` (`attachments` is a reserved morph-relation name). **Importer (`import-from-tft.ts`)** - Import the full content catalog (all types), not just `PARTNER_QUOTE`. - Map TFT `partnerTimezone` → `region`, default `languagesSpoken=[ENGLISH]`, and set `deploymentExpertise=[SELF_HOST]` when scope includes `HOSTING_ENVIRONMENT`. - Filter to partner-relevant records only: opportunities linked to a partner (20 of 164), content linked to a partner (10 of 22). Drops general sales-pipeline / customer-only noise. - Dedupe companies by **normalized domain** (Twenty's unique key), not just name — fixes duplicate-entry crashes when the same company arrives under different names. - Progress logging throughout. **Tooling** - `purge-soft-deleted` script (soft-deleted rows block re-imports via unique constraints). - Multi-env script variants (`*:prod`) selected via `ENV_FILE`. ## Testing Verified on a local Twenty instance and on `partner.twenty.com`: - 122 partners, 20 partner-linked opportunities, 10 partner-linked content (all types), 229 domain-deduped companies. - Schema confirmed via metadata introspection; `yarn twenty typecheck` clean. ## Notes - Renaming an installed object isn't a pure in-place migration on a server that already had `partnerQuote` — the working path is `uninstall → deploy → install` (safe here: prod had no data). 🤖 Generated with [Claude Code](https://claude.com/claude-code) |
||
|
|
7ca9081efa |
Add application installation validation modale (#20907)
## After <img width="1512" height="829" alt="image" src="https://github.com/user-attachments/assets/231d4f0d-6052-4c4e-9a2a-0244d2b3832e" /> |
||
|
|
d602f35cbd |
feat(data-model): custom-indexes management UI and mutations (#20846)
## Summary
Brings indexes management into the per-object Settings tab as a section
under Search (no feature flag, advanced mode only). Admins can create /
delete non-unique indexes with the UI; apps can declare indexes in code
with `defineIndex`. Composite-typed fields are now indexable by picking
a specific sub-column (e.g. `Address > City`).
A few related polish items also land here (invite-user dropdown lands on
the Invite tab; standard warning callout above the new-index form).
## What ships
### UI — custom indexes on per-object Settings
- New section directly under Search, wrapped in
`AdvancedSettingsWrapper`.
- Filter dropdown on the search bar toggles system-index visibility
(shown by default since advanced mode).
- **+ Add Index** button (disabled with tooltip once the per-object cap
is reached) navigates to a dedicated `SettingsObjectNewIndex` page
(matches the field-creation pattern, not a modal):
- Field picker mirrors the webhook event-form layout (rows of dropdowns,
implicit trailing empty row).
- Composite fields surface their sub-properties (`Address > City`,
`Currency > Amount`, …).
- BTREE / GIN type selector.
- Standard warning Callout: "Use indexes sparingly — each one speeds
reads but slows writes."
- Trash icon on `isCustom: true` rows → confirmation modal →
`deleteOneIndex`.
### Server — `createOneIndex` / `deleteOneIndex` mutations
- Gated by `SettingsPermissionGuard(DATA_MODEL)`.
- `IndexMetadataService` wraps the existing migration runner via
`WorkspaceMigrationValidateBuildAndRunService` so the metadata row and
the SQL index land atomically.
- Validation: rejects empty fields, duplicate `(fieldMetadataId,
subFieldName)` pairs, fields not on the object, requires `subFieldName`
for composite parents, forbids `subFieldName` on scalar/relation,
enforces `MAX_CUSTOM_INDEXES_PER_OBJECT = 10`.
- Delete refuses on `isCustom: false` rows so system indexes can't be
removed via this API.
- Dedicated GraphQL exception handler maps each typed error to the right
transport error class.
### Composite sub-field indexing
- Adds `subFieldName: string | null` column to
`IndexFieldMetadataEntity` (fast instance command).
- The flat-entity flow (`UniversalFlatIndexFieldMetadata`,
`FlatIndexFieldMetadata`, `from-universal-flat-index-to-flat-index`,
runner column resolution) all carry `subFieldName` through.
- For composite parents, the runner uses
`computeCompositeColumnName({...}, property)` for the picked sub-column;
for non-composite parents, behavior is unchanged.
- The `'::'` separator encodes `(fieldMetadataId, subFieldName)` for
dedup on the wire; the frontend uses the same separator inside the
Select component's string value.
### Apps can declare indexes in code (`defineIndex`)
- New `IndexManifest` + `IndexFieldManifest` types in
`twenty-shared/application` wired into the `Manifest` type.
- `defineIndex` SDK helper + `IndexConfig`. CLI manifest builder +
extractor recognize `defineIndex` / `ManifestEntityKey.Indexes`.
- Server: `from-index-manifest-to-universal-flat-index` converter
resolves field IDs, validates composite/scalar `subFieldName` rules, and
delegates to `generateFlatIndexMetadataWithNameOrThrow` for the
deterministic name.
- Orchestrator wires the loop after the field-resolution pass;
per-object cap enforced inline against the manifest.
- Cascade on uninstall is automatic — when an app disappears its indexes
drop with it (universal-flat-entity diff handles it).
- Rich-app fixture ships a real `defineIndex` on `PostCard.status`,
exercising the full manifest → install path in CI.
### Closed for now (open later if needed)
- Apps cannot declare `isUnique` indexes — unique constraints stay with
the field-creation flow.
- Apps cannot use a partial-`indexWhereClause` — the UI surface keeps
the framework's hardcoded allowlist.
- UI cannot create unique or partial indexes either; same reasons.
### Cleanups along the way
- Reused the existing `getCompositeSubFieldLabel` +
`COMPOSITE_FIELD_SUB_FIELD_LABELS` (deleted the duplicates I'd created
early in the PR).
- Moved `MAX_CUSTOM_INDEXES_PER_OBJECT` to `twenty-shared/constants`
(single source for FE + BE).
- Replaced inline `isDefined(x) && x !== ''` with `isNonEmptyString`
(from `@sniptt/guards`).
- Hoisted the per-object fields Map + inlined the cap counter into the
indexes orchestrator loop (drops the install scan from O(indexes ×
totalFields) to O(totalFields + indexes)).
- Per design-feedback: page-based create flow (not a modal), filter
dropdown on the SearchInput (not a separate toggle), webhook-style
picker, field icons.
### Unrelated polish that lands here
- "Invite user" link in the multi-workspace dropdown now lands on the
Invite tab directly (`#invite`) instead of the first tab of the members
page.
## Test plan
- [ ] `npx nx typecheck twenty-server / twenty-front / twenty-sdk /
twenty-shared` — passes
- [ ] `npx nx lint:diff-with-main twenty-server / twenty-front` — clean
- [ ] `npx jest index-metadata.service.spec` — green
- [ ] `npx jest from-index-manifest-to-universal-flat-index` — green
(new converter spec, 8 cases)
- [ ] `npx vitest run
src/sdk/define/indexes/__tests__/define-index.spec.ts` (twenty-sdk) —
green (6 cases)
- [ ] `npx vitest run --config vitest.integration.config.ts -t
"rich-app"` — green (rich-app app-dev integration exercises the new
manifest path with the PostCard.status index)
- [ ] Advanced mode → Settings → any object → Settings tab → Indexes
section is visible under Search
- [ ] Create a single-field BTREE index, confirm SQL index exists
(verify via `pg_indexes`)
- [ ] Create a composite-field index (`Address > City`) and confirm the
column is `addressAddressCity`
- [ ] Create an index spanning two columns; column order matches the
picker order
- [ ] Attempt to create an 11th custom index → button is disabled with
tooltip
- [ ] Delete a custom index → confirmation modal → row disappears, PG
index dropped
- [ ] System indexes have no trash icon and are hidden by default
|
||
|
|
b8b115f4e3 |
FileStorageService Dedicated file and folder code flow + integrity check (#20831)
# Introduction
Next handling mimetype integrity check and checksum integrity check for
s3 storage type
Always expecting a trailing end slash when deleting a folder etc
## Application
Uninstalling an application now deletes all its related files
## File storage service
Making a distincton between folder path and file path
## Validation Pipeline
Every file operation in `FileStorageService.buildOnStoragePath` runs
through `validateResourcePath`, which chains three validators in order:
**1. `validateSafeRelativePath`** -- rejects path traversal attacks
| Input | Result | Error |
|---|---|---|
| `../../../etc/passwd` | Rejected | `Resource path must not contain
path traversal (..)` |
| `/etc/passwd` | Rejected | `Resource path must be relative, not
absolute` |
| `file\0.txt` | Rejected | `Resource path contains null bytes` |
| `..\\..\\etc\\passwd` | Rejected | `Resource path must not contain
backslashes` |
| _(empty)_ | Rejected | `Resource path must not be empty` |
**2. `validateFilenameIntegrity`** -- enforces safe characters, length
limits, extension required
| Input | Result | Error |
|---|---|---|
| `my folder/file.mjs` | Rejected | `A path segment contains invalid
characters...` |
| `Makefile` | Rejected | `Filename must have an extension` |
| `aaa...(256 chars).mjs` | Rejected | `A path segment exceeds the
maximum length of 255 characters` |
| `a/b/.../file.mjs` (1025+ chars) | Rejected | `Resource path exceeds
maximum length of 1024 characters` |
| `src/handlers/index.mjs` | Accepted | -- |
| `my-app/my_file.tsx` | Accepted | -- |
| `v1.0/module.config.mjs` | Accepted | -- |
Allowed characters per segment: `a-z`, `A-Z`, `0-9`, `.`, `-`, `_`
**3. `validateResourceExtension`** -- checks extension against the
`FileFolder` allowlist
| Input | FileFolder | Result | Error |
|---|---|---|---|
| `handler.js` | `BuiltLogicFunction` | Rejected | `Invalid file
extension. Allowed extensions: .mjs` |
| `card.tsx` | `BuiltFrontComponent` | Rejected | `Invalid file
extension. Allowed extensions: .mjs` |
| `script.js` | `PublicAsset` | Rejected | `Invalid file extension.
Allowed extensions: .png, .jpg, ...` |
| `index.mjs` | `BuiltLogicFunction` | Accepted | -- |
| `app.tsx` | `Source` | Accepted | -- |
| `photo.png` | `CorePicture` | Accepted | -- (unconfigured folder,
passes through) |
## Consumers
- **`FileStorageService`** -- calls `validateResourcePath`, throws
`FileStorageException` on failure (last-resort defense)
- **Resolver (`uploadApplicationFile`)** -- calls
`validateResourcePath`, throws `ApplicationException` on failure
(user-facing)
- **Flat validators** -- call `validateResourcePath`, push the error to
`validationResult.errors` (non-throwing, collects all errors)
All error messages are translated via Lingui `t` and returned in a
discriminated union `{ isValid: true } | { isValid: false, error: string
}`, letting each consumer decide how to handle failures.
|
||
|
|
3bda05ea57 |
[Breaking change] Prepare non-system permission flags (#20847)
# Summary Replaces the enum-keyed `permissionFlags: PermissionFlag[]` on roles with `permissionFlagUniversalIdentifiers: string[]` This unlocks mixing system flags (`SystemPermissionFlag.*`) with app-defined flags in a role config. This is a breaking change. Existing app source must switch to the new field. # Breaking changes - `RoleManifest.permissionFlags` removed. Use `RoleManifest.permissionFlagUniversalIdentifiers: string[]`. - `RoleConfig.permissionFlags` removed (was `PermissionFlagType[]`). Use `RoleConfig.permissionFlagUniversalIdentifiers: string[]`. - `PermissionFlagManifest` type removed from `twenty-shared/application`. - `PermissionFlag` re-export removed from `twenty-sdk/define`. `SystemPermissionFlag` is re-exported in its place. - Retargeting a permission flag between roles is now classified as delete + create instead of update ### Not in this PR - definePermissionFlag SDK function and top-level Manifest.permissionFlags catalog (apps defining their own custom flags). Until those land, permissionFlagUniversalIdentifiers only accepts SystemPermissionFlag.* UUIDs; arbitrary UUIDs fail validation. |
||
|
|
11b9f708d6 |
feat(twenty-partners): add partners app (#20792)
## Summary - Adds `twenty-partners`, a Twenty app that manages the partner matching pipeline: intake partner-eligible deals, assign vetted marketplace partners, and track the full funnel - Custom `Partner` object with availability, geo/language coverage, deployment expertise, and Calendly link - `matchStatus` SELECT field on Opportunity — 10 non-nullable states from `TO_BE_MATCHED` through `WON`/`LOST`, replacing a legacy boolean approach - Auto-match logic function: when `matchStatus` → `AUTO_MATCH`, assigns the longest-idle available partner and advances to `MATCHED`; falls back to `MANUAL_MATCH` with an audit note if no partner is free - Views: Waiting for match, Matches overview (Kanban by `matchStatus`), All matched deals, Partners, Opportunities - Roles: Partner Ops (internal, full CRUD) and Partner (external placeholder) - Idempotent seed scripts for demo partners and pipeline data ## Test plan - [ ] App installs cleanly on a fresh workspace (`yarn twenty dev`) - [ ] `matchStatus` Kanban grouping renders correctly in Matches overview - [ ] Waiting for match view filters to `TO_BE_MATCHED` and `MANUAL_MATCH` only - [ ] Auto-match logic assigns a partner and advances status - [ ] Seed scripts run without errors and are safe to re-run |
||
|
|
237a943947 |
Update twenty sdk commands (#20735)
Performs twenty-sdk cli command migration: Summary ``` ┌─────┬──────────────────────────┬────────────────────────────┬───────────────────────┐ │ # │ Old command │ New command │ Status │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 1 │ twenty dev [appPath] │ twenty dev [appPath] │ Unchanged (now also │ │ │ │ │ DEFAULT) │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 2 │ twenty dev --once │ twenty dev --once │ Unchanged │ │ │ [appPath] │ [appPath] │ │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 3 │ twenty dev --watch │ twenty dev [appPath] │ --watch flag removed │ │ │ [appPath] │ │ (was default) │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 4 │ twenty dev --verbose │ twenty dev --verbose │ Unchanged │ │ │ [appPath] │ [appPath] │ │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 5 │ twenty dev --debug │ twenty dev --debug │ Unchanged │ │ │ [appPath] │ [appPath] │ │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 6 │ twenty dev --debounceMs │ twenty dev --debounceMs │ Unchanged │ │ │ <ms> [appPath] │ <ms> [appPath] │ │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 7 │ twenty build [appPath] │ twenty dev:build [appPath] │ Deprecated → colon │ │ │ │ │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 8 │ twenty build --tarball │ twenty dev:build --tarball │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 9 │ twenty typecheck │ twenty dev:typecheck │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 10 │ twenty logs [appPath] │ twenty dev:fn-logs │ Deprecated → colon │ │ │ │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 11 │ twenty logs -n <name> │ twenty dev:fn-logs -n │ Deprecated → colon │ │ │ [appPath] │ <name> [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 12 │ twenty logs -u <id> │ twenty dev:fn-logs -u <id> │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 13 │ twenty exec [appPath] │ twenty dev:fn-exec │ Deprecated → colon │ │ │ │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 14 │ twenty exec -n <name> │ twenty dev:fn-exec -n │ Deprecated → colon │ │ │ [appPath] │ <name> [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 15 │ twenty exec -u <id> │ twenty dev:fn-exec -u <id> │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 16 │ twenty exec -p <json> │ twenty dev:fn-exec -p │ Deprecated → colon │ │ │ [appPath] │ <json> [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 17 │ twenty exec │ twenty dev:fn-exec │ Deprecated → colon │ │ │ --postInstall [appPath] │ --postInstall [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 18 │ twenty exec --preInstall │ twenty dev:fn-exec │ Deprecated → colon │ │ │ [appPath] │ --preInstall [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 19 │ twenty add [entityType] │ twenty dev:add │ Deprecated → colon │ │ │ │ [entityType] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 20 │ twenty add --path <path> │ twenty dev:add --path │ Deprecated → colon │ │ │ [entityType] │ <path> [entityType] │ command │ └─────┴──────────────────────────┴────────────────────────────┴───────────────────────┘ App lifecycle commands ┌─────┬────────────────────────┬────────────────────────────┬─────────────────────────┐ │ # │ Old command │ New command │ Status │ ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤ │ 21 │ twenty publish │ twenty app:publish │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤ │ 22 │ twenty publish --tag │ twenty app:publish --tag │ Deprecated → colon │ │ │ <tag> [appPath] │ <tag> [appPath] │ command │ ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤ │ 23 │ twenty deploy │ twenty app:publish │ Deprecated → colon │ │ │ [appPath] │ --private [appPath] │ command + --private │ ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤ │ 24 │ twenty install │ twenty app:install │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤ │ 25 │ twenty uninstall │ twenty app:uninstall │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤ │ 26 │ twenty uninstall -y │ twenty app:uninstall -y │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ └─────┴────────────────────────┴────────────────────────────┴─────────────────────────┘ Server commands ┌─────┬─────────────────────────┬─────────────────────────────┬──────────────────────┐ │ # │ Old command │ New command │ Status │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 27 │ twenty server start │ twenty docker:start │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 28 │ twenty server start -p │ twenty docker:start -p │ Deprecated → colon │ │ │ <port> │ <port> │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 29 │ twenty server start │ twenty docker:start --test │ Deprecated → colon │ │ │ --test │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 30 │ twenty server stop │ twenty docker:stop │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 31 │ twenty server stop │ twenty docker:stop --test │ Deprecated → colon │ │ │ --test │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 32 │ twenty server status │ twenty docker:status │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 33 │ twenty server status │ twenty docker:status --test │ Deprecated → colon │ │ │ --test │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 34 │ twenty server logs │ twenty docker:logs │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 35 │ twenty server logs -n │ twenty docker:logs -n │ Deprecated → colon │ │ │ <lines> │ <lines> │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 36 │ twenty server logs │ twenty docker:logs --test │ Deprecated → colon │ │ │ --test │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 37 │ twenty server reset │ twenty docker:reset │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 38 │ twenty server reset │ twenty docker:reset --test │ Deprecated → colon │ │ │ --test │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 39 │ twenty server upgrade │ twenty docker:upgrade │ Deprecated → colon │ │ │ [version] │ [version] │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 40 │ twenty server upgrade │ twenty docker:upgrade │ Deprecated → colon │ │ │ --test [version] │ --test [version] │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 41 │ twenty server │ twenty app:catalog-sync │ Deprecated → colon │ │ │ catalog-sync │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 42 │ twenty server │ twenty app:catalog-sync │ Deprecated → colon │ │ │ catalog-sync -r <name> │ -r <name> │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 43 │ twenty catalog-sync │ (removed) │ Removed (was already │ │ │ │ │ deprecated) │ └─────┴─────────────────────────┴─────────────────────────────┴──────────────────────┘ Remote commands ┌─────┬────────────────────────┬──────────────────────────┬──────────────────────────┐ │ # │ Old command │ New command │ Status │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 44 │ twenty remote add │ twenty remote:add │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 45 │ twenty remote add --as │ twenty remote:add --as │ Deprecated → colon │ │ │ <name> │ <name> │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 46 │ twenty remote add │ twenty remote:add │ Deprecated → colon │ │ │ --api-key <key> │ --api-key <key> │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 47 │ twenty remote add │ twenty remote:add │ Deprecated → colon │ │ │ --api-url <url> │ --api-url <url> │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 48 │ twenty remote add │ twenty remote:add │ Deprecated → colon │ │ │ --local │ --local │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 49 │ twenty remote add │ twenty remote:add --test │ Deprecated → colon │ │ │ --test │ │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 50 │ twenty remote list │ twenty remote:list │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 51 │ twenty remote switch │ twenty remote:use [name] │ Deprecated → colon │ │ │ [name] │ │ syntax + renamed │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 52 │ twenty remote status │ twenty remote:status │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 53 │ twenty remote remove │ twenty remote:remove │ Deprecated → colon │ │ │ <name> │ <name> │ syntax │ └─────┴────────────────────────┴──────────────────────────┴──────────────────────────┘ ``` --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> |
||
|
|
c800eccc65 |
Slack workflow connector (#20427)
https://github.com/user-attachments/assets/5a746414-988b-473c-9401-b8863a3e1c15 https://github.com/user-attachments/assets/0cdebdb1-f7c8-43cb-beef-f279387b6ce9 https://github.com/user-attachments/assets/df31c631-0781-42d8-8e6e-e5a16573ee3b https://github.com/user-attachments/assets/6adaeae4-f3c9-4a5f-b0df-50c1f9a78428 --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: martmull <martmull@hotmail.fr> |
||
|
|
d345a6b2d6 |
Twenty fireflies integration (#20618)
Co-authored-by: martmull <martmull@hotmail.fr> |
||
|
|
d5ff9eb515 |
Create twenty app improvements (#20688)
create-twenty-app updates: - remove --example option - sync --once when scaffolding an applicaiton - rename --api-url option to --workspace-url - create a standalone page when scaffolding an app <img width="1494" height="765" alt="image" src="https://github.com/user-attachments/assets/0e35ed0c-b0aa-466c-9f56-7939294fd2cf" /> --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> |
||
|
|
fc53f18a9f |
Twenty discord integration (#20530)
<img width="1290" height="777" alt="Screenshot 2026-05-14 at 8 55 02 AM" src="https://github.com/user-attachments/assets/37cde89b-b4c3-438a-8ccf-39621f9799b4" /> https://github.com/user-attachments/assets/cd8af6f9-f2e5-47cf-bf1f-57590bb358d1 https://github.com/user-attachments/assets/ff53c6b7-ee1a-42db-bbc5-d7df5b3fa6b0 https://github.com/user-attachments/assets/ddca2c16-774d-4672-aa61-05e878d8b2b7 |